📘

React勉強しなおしてみた #2

2024/02/27に公開

前回記事はこちら

本記事の内容

元JavaエンジニアがReactを再学習する記録。
前回はクイックスタートで大まかに概要を掴んだ。今回以降、より詳細に学習していく。
本記事内では下記セクションを学習する。

  • UIの記述

UIの記述

公式学習ページ
前回作成したプロジェクトを引き続き使用して学習していく。

初めてのコンポーネント

コンポーネント:UIの構成部品

<h1><li>などのHTML、CSS、JavaScriptをまとめた再利用可能なUI要素をコンポーネントと呼ぶ。
ReactではHTMLタグを使用するようにコンポーネントを組み合わせたり、ネストしたりしてページ全体をデザインすることが出来る。
例としてHomeを下記のように変更してみた。

src/layouts/PageLayout.tsx
import Head from "next/head";
import styles from "@/styles/Home.module.css";
import { Inter } from "next/font/google";
import React from "react";

const inter = Inter({ subsets: ["latin"] });

type Props = {
  title: string;
  children: React.ReactNode;
};
export function PageLayout(props: Props) {
  return (
    <>
      <Head>
        <title>{props.title}</title>
        <meta name="description" content={props.title} />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={`${styles.main} ${inter.className}`}>
        <h1>{props.title}</h1>
        {props.children}
      </main>
    </>
  );
}
src/pages/index.tsx
import MyButton from "@/components/Button/MyButton";
import MyInput from "@/components/Input/MyInput";
import { MyList } from "@/components/List/MyList";
import { useState } from "react";
import { PageLayout } from "@/layouts/PageLayout";

export default function Home() {
  const [value, setValue] = useState("I`m a input");
  function handleChange(value: string) {
    setValue(value);
  }
  return (
    <PageLayout title="React Learn">
      <MyInput value={value} disable={true} onChange={handleChange} />
      <MyInput value={value} disable={false} onChange={handleChange} />
      <MyList />
      <MyButton />
    </PageLayout>
  );
}

これでHeadの内容とmainタグを共通化することができた。

コンポーネントの定義

Reactコンポーネントとは、HTMLマークアップを添えることができるJavaScript関数である。
(前回さらっと定義してしまっているが)定義の内容を詳しく見ていく。

export

コンポーネントを定義した際頭につけたexportexport defaultとも記述可能)は、ファイル内の関数(あるいは定数)を他ファイルからimportできるようにする。

src/components/Button/MyButton.ts
export default function MyButton() {
// ...
src/components/List/MyList.tsx
// ...
export function MyList() {
// ...

importする際の違いは下記の通り。
export defaultは一つのファイルにつき一つのみ定義可能なのに対して、exportは複数定義可能。
詳しい違いは「【React/Next.js】「export default」と「export」の違い」

src/pages/index.tsx
// export defaultの場合
import MyButton from "@/components/Button/MyButton";
// exportの場合
import { MyList } from "@/components/List/MyList";

関数を定義

Reactコンポーネントは普通のJavaScript関数だが、名前は大文字から始まらなければならない。

src/components/List/MyList.tsx
export function MyList() {
// ...

マークアップ

returnでタグ(以下の場合はliタグ)を返している。
一行で収まる場合はreturn <></>で問題ないが、複数行になる場合はreturn (<></>)としなければならない。
例は前回作成したMyListの中身をコンポーネントとして切り出したもの。

src/components/List/MyList.tsx
type ListItemProps = Color;
export function MyListItem(props: ListItemProps) {
  const { id, title } = props;
  return (
    <li style={{ display: "flex", gap: 10 }}>
      <div style={{ width: 20, height: 20, backgroundColor: id }}></div>
      {title}
    </li>
  );
}

コンポーネントを使う

定義したコンポーネントを実際に使用する。
例として先ほど切り出したMyListItemコンポーネントをMyListコンポーネント内に配置する。

src/components/List/MyList.tsx
export function MyList() {
  return (
    <ul>
      {colors.map((color) => {
        return <MyListItem key={color.id} {...color} />;
      })}
    </ul>
  );
}

これを実際にブラウザで確認すると下記のようになる。

...
<ul>
  <li style="display: flex; gap: 10px;">
    <div style="width: 20px; height: 20px; background-color: red;"></div></li>
  <li style="display: flex; gap: 10px;">
    <div style="width: 20px; height: 20px; background-color: blue;"></div></li>
  <li style="display: flex; gap: 10px;">
    <div style="width: 20px; height: 20px; background-color: yellow;"></div>
    黄色
  </li>
</ul>
...

Reactは<main><h1>など小文字で始まるものをHTMLタグ、<MyButton><PageLayout>など大文字で始まるものをコンポーネントだと解釈する。

コンポーネントのネストと整理方法

コンポーネントは普通のJavaScript関数であるため、MyList, MyListItemコンポーネントのように同一ファイル内に複数定義することも可能。

src/components/List/MyList.tsx
type Color = {
  id: string;
  title: string;
};
const colors: Color[] = [
  { id: "red", title: "赤" },
  { id: "blue", title: "青" },
  { id: "yellow", title: "黄色" },
];

export function MyList() {
  return (
    <ul>
      {colors.map((color) => {
        return <MyListItem key={color.id} {...color} />;
      })}
    </ul>
  );
}

type ListItemProps = Color;
function MyListItem(props: ListItemProps) {
  const { id, title } = props;
  return (
    <li style={{ display: "flex", gap: 10 }}>
      <div style={{ width: 20, height: 20, backgroundColor: id }}></div>
      {title}
    </li>
  );
}

MyListItemコンポーネントはMyListコンポーネント内でレンダーされている。そのため、MyListは親コンポーネントであり、MyListItemコンポーネントを子としてレンダーしているということができる。
ただし、コンポーネントの定義はネストできない。

// Bad
export function MyList() {
  function MyListItem() {
    // ...
  }
  // ...
}

// OK
export function MyList() {
  // ...
}
function MyListItem() {
  // ...
}

コンポーネントのインポートとエクスポート

ルートコンポーネントファイル

今回使用しているサンプルプロジェクトはNext.jsを利用しているためscr/pages/index.tsxがルートコンポーネントファイルとなる。

コンポーネントのインポートとエクスポート

先ほどsrc/components/List/MyList.tsx内に作成したMyListItemコンポーネントを別ファイルに分割する。
まずsrc/components/List/MyListItem.tsxを作成、MyListItemの内容を移動させる。先ほどは同ファイル内でしか使用していなかったため、MyListItemをexportしていなかったが、別ファイルに切り出したためexportする。

src/components/List/MyListItem.tsx
import { Color } from "./MyList";

type ListItemProps = Color;
export function MyListItem(props: ListItemProps) {
  const { id, title } = props;
  return (
    <li style={{ display: "flex", gap: 10 }}>
      <div style={{ width: 20, height: 20, backgroundColor: id }}></div>
      {title}
    </li>
  );
}

MyListItem内で使用するためColorをexportし、MyListにMyListItemをimportして完了。

src/components/List/MyList.tsx
import { MyListItem } from "./MyListItem";

export type Color = {
  id: string;
  title: string;
};
const colors: Color[] = [
  { id: "red", title: "赤" },
  { id: "blue", title: "青" },
  { id: "yellow", title: "黄色" },
];

export function MyList() {
  return (
    <ul>
      {colors.map((color) => {
        return <MyListItem key={color.id} {...color} />;
      })}
    </ul>
  );
}

同じファイルから複数のコンポーネントをエクスポート・インポートする

先述の通りdefault exportは複数存在できないため、Named export(defaultを付けないただのexport)を使用する。default exportとNamed exportは共存できる。
例としてsrc/components/Button/MyButton.tsxにCancelButtonを追加してみた。

src/components/Button/MyButton.tsx
export default function MyButton() {
  const props = {
    className: "normal-button",
    backgroundColor: "blue",
    color: "white",
    title: "I`m a normal button",
  };
  function handleClick() {
    alert("You clicked me!");
  }
  return (
    <button
      className={props.className}
      style={{ backgroundColor: props.backgroundColor, color: props.color }}
      onClick={handleClick}
    >
      {props.title}
    </button>
  );
}

export function CancelButton() {
  return (
    <button className="cancel-button" style={{ backgroundColor: "red", color: "white" }}>
      I&apos;m a cancel button
    </button>
  );
}

importはこうなる。

src/pages/index.tsx
import MyButton, { CancelButton } from "@/components/Button/MyButton";

JSX でマークアップを記述する

JSXとはJavaScriptファイル内にHTMLのようなマークアップを書けるよう拡張したもの。

JSX: JavaScript にマークアップを入れ込む

従来のweb開発ではコンテンツをHTML、デザインはCSS、ロジックをJavaScriptで書き、それらを別ファイルにしていたが、Reactではロジックとマークアップを同じ場所に書く。
レンダリングロジックとマークアップを同じ場所に書くことで、毎回の編集時に同期されることが保証される。逆に直接関係ないコンポーネントの内容は分離されるため、それぞれをより安全に独立して更新できるようになる。
個々のReactコンポーネントはJavaScript関数であり、内部にマークアップを含めることができる。そのマークアップを表現するため、ReactコンポーネントはJSXを使用する。
JSXはHTMLと似ているが、より構文が厳密である。

HTMLをJSX に変換する

以降公式ページに載っているサンプルを使用して試してみる。

サンプル
<h1>Hedy Lamarr's Todos</h1>
<img 
  src="https://i.imgur.com/yXOvdOSs.jpg" 
  alt="Hedy Lamarr" 
  class="photo"
>
<ul>
    <li>Invent new traffic lights
    <li>Rehearse a movie scene
    <li>Improve the spectrum technology
</ul>

このままコピペしても当然エラーになる。

JSX のルール

単一のルート要素を返す

コンポーネントは一つの要素しか返せない。そのため、複数の要素を返すには<div>タグで囲む。余分な要素を加えたくない場合は、<></>(Fragment)を使用する。
今回サンプルではFragmentを使用した。

サンプル
<>
  <h1>Hedy Lamarr's Todos</h1>
  <img 
    src="https://i.imgur.com/yXOvdOSs.jpg" 
    alt="Hedy Lamarr" 
    class="photo"
  >
  <ul>
      <li>Invent new traffic lights
      <li>Rehearse a movie scene
      <li>Improve the spectrum technology
  </ul>
</>

すべてのタグを閉じる

JSXではすべてのタグを明示的に閉じなければならない。<img>のような自己完結タグは<img />となり、囲みタグは必ず閉じタグを書かなければならない。
以上の点に留意してサンプルを修正する。

サンプル
<>
  <h1>Hedy Lamarr's Todos</h1>
  <img 
    src="https://i.imgur.com/yXOvdOSs.jpg" 
    alt="Hedy Lamarr" 
    class="photo"
  /> <!-- 自己完結タグの場合"/>"とする -->
  <ul>
      <li>Invent new traffic lights</li> <!-- 閉じタグを必ず書く -->
      <li>Rehearse a movie scene</li> 
      <li>Improve the spectrum technology</li> 
  </ul>
</>

(ほぼ)すべてキャメルケースで!

JSXはJavaScriptに変換され、中に書かれた属性はJavaScriptのキーとなる。そのため属性名はJavaScriptの制約を受けることになる。例えばハイフンを含めた名前や、classなどの予約語は使用できない。そのためReactではキャメルケースで書く。
例えばstroke-widthstrokeWidthclassclassNameとなる。
ただし、area-*data-*はHTML属性と同じようにハイフン付きで書く。
以上の点に留意してサンプルを修正する。

サンプル
<>
  <h1>Hedy Lamarr's Todos</h1>
  <img 
    src="https://i.imgur.com/yXOvdOSs.jpg" 
    alt="Hedy Lamarr" 
    className="photo" <!-- "class"は予約語のため、代わりに"className"を使う -->
  />
  <ul>
      <li>Invent new traffic lights</li>
      <li>Rehearse a movie scene</li> 
      <li>Improve the spectrum technology</li> 
  </ul>
</>

ヒント:JSX コンバータを使う

上記のことをいちいち手動でやるのは面倒くさいので、HTMLやSVGをJSXに変換する際にはコンバータが利用したい。
ちなみにサンプルは以下のようになる。

サンプル
export default function TodoList() {
  return (
    <>
      <h1>Hedy Lamarr's Todos</h1>
      <img 
        src="https://i.imgur.com/yXOvdOSs.jpg" 
        alt="Hedy Lamarr" 
        className="photo" 
      />
      <ul>
        <li>Invent new traffic lights</li>
        <li>Rehearse a movie scene</li>
        <li>Improve the spectrum technology</li>
      </ul>
    </>
  );
}

JSXに波括弧でJavaScriptを含める

JSX内で動的なプロパティを利用したい場合、{}を使用することで実現できる。

引用符で文字列を渡す

先ほど作成したCancelButtonを例に見ていく。
JSXに文字列の属性を渡したい場合、''もしくは""で囲む。例ではclassName"cancel-buttonstylebackgroundColor, colorにそれぞれ"red", "white"を渡している。

src/components/Button/MyButton.tsx
export function CancelButton() {
  return (
    <button className="cancel-button" style={{ backgroundColor: "red", color: "white" }}>
      I&apos;m a cancel button
    </button>
  );
}

これを動的に変更したい場合は、下記のMyButtonのように{}で囲む。こうすることでJSX内で直接JavaScriptを使用できる。

src/components/Button/MyButton.tsx
export default function MyButton() {
  const props = {
    className: "normal-button",
    backgroundColor: "blue",
    color: "white",
    title: "I`m a normal button",
  };
  function handleClick() {
    alert("You clicked me!");
  }
  return (
    <button
      className={props.className}
      style={{ backgroundColor: props.backgroundColor, color: props.color }}
      onClick={handleClick}
    >
      {props.title}
    </button>
  );
}

波括弧は JavaScript 世界への窓口

JSXはJavaScriptを書く方法の一つであり、その中でJavaScriptを使用することも当然できる。その手段が{}である。
試しにpropsで受け取ったページタイトルの横にアプリのタイトルを入れてみた。

src/layouts/PageLayout.tsx
// ...
export function PageLayout(props: Props) {
  return (
    <>
      <Head>
        <title>{props.title} | React Learn</title>
        {// ...}
      </Head>
      {// ...}
    </>
  );
}

{}内には関数の呼び出し等あらゆるJavaScriptが動作する。
例ではPageLayoutに当日の日付を表示させた。

src/layouts/PageLayout.tsx
// ...
export function PageLayout(props: Props) {
  const today = new Date();
  return (
    <>
      {// ...}
      <main className={`${styles.main} ${inter.className}`}>
        <h1>{props.title}</h1>
        <div>{new Intl.DateTimeFormat("ja-JP").format(today)}</div>
        {props.children}
      </main>
    </>
  );
}

波括弧を使える場所

JSX内で{}を使用できるのは2パターン。

  1. テキストとして、JSXタグ内で直接使用(例:<title>{props.title} | React Learn</title>
  2. 属性として、=の後に使用(例:<button className={props.className}></button>

「ダブル波括弧」でJSX内にCSS やその他のオブジェクトを含める

文字列、数字、JavaScriptの式の他、オブジェクトをJSXに渡すことも可能。
下記の例のstyleのようにオブジェクト{ backgroundColor: "red", color: "white" }{}でラップして{{ backgroundColor: "red", color: "white" }}のようにする必要がある。
またbackground-colorbackgroundColorとしているように、style属性もキャメルケースで書く必要がある。

src/components/Button/MyButton.tsx
export function CancelButton() {
  return (
    <button className="cancel-button" style={{ backgroundColor: "red", color: "white" }}>
      I&apos;m a cancel button
    </button>
  );
}

コンポーネントにpropsを渡す

Reactコンポーネントは互いにやり取りをする際propsを使用する。親コンポーネントは子コンポーネントにpropsを渡すことで情報を伝えることができる。propsではオブジェクトや配列、関数等あらゆるJavaScriptの値を渡すことができる。

お馴染みのprops

propsとはJSXタグに渡す情報のこと。例えばimgにはclassName, src, alt, width, heightをpropsとして渡すことができる。
img等HTML標準に準拠したReactDOMなら渡すことのできるpropsは事前に決められているが、独自のコンポーネントの場合任意のpropsを渡すことができる。

コンポーネントにpropsを渡す

例としてMyButtonを使用する。現在のMyButtonは以下のように定義されている。

src/components/Button/MyButton.tsx
export default function MyButton() {
  const props = {
    className: "normal-button",
    backgroundColor: "blue",
    color: "white",
    title: "I`m a normal button",
  };
  function handleClick() {
    alert("You clicked me!");
  }
  return (
    <button
      className={props.className}
      style={{ backgroundColor: props.backgroundColor, color: props.color }}
      onClick={handleClick}
    >
      {props.title}
    </button>
  );
}

子コンポーネントにpropsを渡す

MyButton内で定義されているpropsの内容を親コンポーネントからpropsとして渡す。

src/pages/index.tsx
// ...
export default function Home() {
  // ...
  return (
    <PageLayout title="React Learn">
      {// ...}
      <MyButton
        className="normal-button"
        backgroundColor="blue"
        color="white"
        title="I`m a normal button"
      />
    </PageLayout>
  );
}

子コンポーネントからpropsを読み出す

引数として先ほど渡したpropsを受け取る。
propsを使用することで、親と子コンポーネントを独立して考えることができるようになる。

src/components/Button/MyButton.tsx
type Props = {
  className: string;
  backgroundColor: string;
  color: string;
  title: string;
};
export default function MyButton({ className, backgroundColor, color, title }: Props) {
  function handleClick() {
    alert("You clicked me!");
  }
  return (
    <button
      className={className}
      style={{ backgroundColor: backgroundColor, color: color }}
      onClick={handleClick}
    >
      {title}
    </button>
  );
}

propsのデフォルト値を指定する

propsに値が渡されなかった際使用するデフォルト値を設定したい場合、分割代入の中でパラメータ名の直後に=と続けて値を指定できる。

src/components/Button/MyButton.tsx
export default function MyButton({
  className,
  backgroundColor,
  color = "white", // colorが指定されなかった場合、デフォルト値"white"が使用される
  title }: Props) {
// ...
}

JSXスプレッド構文でpropsを転送する

propsの受け渡しにはスプレッド構文を使用することも可能。先ほどのMyButtonの例なら下記のようにできる。

src/pages/index.tsx
// ...
export default function Home() {
  // ...
  const buttonProps = {
    className: "normal-button",
    backgroundColor: "blue",
    color: "white",
    title: "I`m a normal button",
  };
  return (
    <PageLayout title="React Learn">
      {// ...}
      <MyButton {...buttonProps} />
    </PageLayout>
  );
}

childrenとしてJSXを渡す

組み込みタグと同様に独自コンポーネントもネストできる。その場合、親コンポーネントは中身をchildrenというpropsとして受け取る。

src/layouts/PageLayout.tsx
// ...
type Props = {
  title: string;
  children: React.ReactNode;
};
export function PageLayout(props: Props) {
  return (
    <>
      {// ...}
      <main className={`${styles.main} ${inter.className}`}>
        <h1>{props.title}</h1>
        {props.children}
      </main>
    </>
  );
}

propsは時間とともに変化する

propsとはコンポーネントの最初の時点ではなく、任意の時点でのコンポーネントのデータを反映するものである。しかし、propsは不変でなければならない。そのためコンポーネントのpropsがユーザーの操作等で変わらなければならない場合、親コンポーネントから新しいオブジェクトを渡してもらう必要がある。

条件付きレンダー

条件を満たす場合にJSXを返す

if文によって返すJSXを変更する。
例としてMyTitleコンポーネントを作成した。withMarkの値によって返す内容が変わる。

src/components/Title/MyTitle.tsx
type Props = {
  title: string;
  withMark: boolean;
};
export function MyTitle(props: Props) {
  if (props.withMark) {
    return <h1>{props.title}</h1>;
  }
  return <h1>{props.title}</h1>;
}

nullを使って何も返さないようにする

何もレンダーしたくない場合、nullを返してしまえばいい。
例としてMyListItemを変更してみた。isDisliketrueであれば何も表示されない。

src/components/List/MyListItem.tsx
import { Color } from "./MyList";

type ListItemProps = Color & {
  isDislike: boolean;
};
export function MyListItem(props: ListItemProps) {
  const { id, title, isDislike } = props;
  if (isDislike) {
    return null;
  }
  return (
    <li style={{ display: "flex", gap: 10 }}>
      <div style={{ width: 20, height: 20, backgroundColor: id }}></div>
      {title}
    </li>
  );
}

条件付きでJSXを含める

条件(三項)演算子 (?:)

JSX内で三項演算子を使用することもできる。
先ほど作成したMyTitleを三項演算子に変更すると以下のようになる。

src/components/Title/MyTitle.tsx
export function MyTitle(props: Props) {
  return <h1>{props.withMark ? `${props.title}` : props.title}</h1>;
}

論理AND演算子(&&)

&&を使用した例。条件が真の場合レンダーされ、それ以外の場合何もレンダーされない。
ただし&&の左辺が0の場合、式全体が0と評価されてしまうため0が表示されてしまう。count && <p>message</>とするのではなく、count > 0 && <p>message</>とする必要がある。

src/components/Title/MyTitle.tsx
export function MyTitle(props: Props) {
  return (
    <h1>
      {props.withMark && "◇ "}
      {props.title}
    </h1>
  );
}

条件付きでJSXを変数に割り当てる

if文とletを使用して記述することもできる。MyTitleをこの形に変更してみると下記のようになる。

src/components/Title/MyTitle.tsx
export function MyTitle(props: Props) {
  let h1Title = props.title;
  if (props.withMark) {
    h1Title = "◇ " + props.title;
  }
  return <h1>{h1Title}</h1>;
}

リストのレンダー

配列からデータをレンダー

mapを利用して配列からアイテムのリストを生成する。
下記は以前作成したMyListの例。colors配列の内容をリストにして表示する。

src/components/List/MyList.tsx
import { MyListItem } from "./MyListItem";

export type Color = {
  id: string;
  title: string;
};
const colors: Color[] = [
  { id: "red", title: "赤" },
  { id: "blue", title: "青" },
  { id: "yellow", title: "黄色" },
];

export function MyList() {
  return (
    <ul>
      {colors.map((color) => {
        return <MyListItem {...color} />;
      })}
    </ul>
  );
}

アイテムの配列をフィルタする

filterを使用して配列を絞り込んで表示させる。
下記の例ではColortoneを増やし、warmのものだけ表示するよう変更した。

src/components/List/MyList.tsx
import { MyListItem } from "./MyListItem";

export type Color = {
  id: string;
  title: string;
  tone: "warm" | "cold";
};
const colors: Color[] = [
  { id: "red", title: "赤", tone: "warm" },
  { id: "blue", title: "青", tone: "cold" },
  { id: "yellow", title: "黄色", tone: "warm" },
];

export function MyList() {
  const warmColors = colors.filter((color) => color.tone === "warm");
  return (
    <ul>
      {warmColors.map((color) => {
        return <MyListItem {...color} />;
      })}
    </ul>
  );
}

keyによるリストアイテムの順序の保持

配列の各アイテムにはkeyを渡す必要がある。これは配列内の他アイテムと区別するための一意な文字列あるいは数値のことである。keyは配列のどの要素がどのコンポーネントに対応するのかをReactが判断し、後で更新するために必要になる。配列内の要素が移動、削除、追加された場合、適切なkeyを指定することで、DOMツリーに正しい更新を反映させることができる。

src/components/List/MyList.tsx
// ...
export function MyList() {
  const warmColors = colors.filter((color) => color.tone === "warm");
  return (
    <ul>
      {warmColors.map((color) => {
        return <MyListItem key={color.id} {...color} />; // idをkeyとして指定した
      })}
    </ul>
  );
}

keyをどこから得るのか

  • データベースからのデータ:データベースから取得したデータの場合、キーやIDは必然的に一位であるためそれを利用できる。
  • ローカルで生成されたデータ:ローカルで生成したデータの場合、アイテムを作成する際に、インクリメンタルなカウンタあるいはcrypto.randomUUID()uuidなどのパッケージを使用する。

keyのルール

  • キーは兄弟間で一位でなければならない。ただし、別の配列に対応するJSXノードには同じキーを使用できる。
  • キーは変更してはいけない。key={Math.random()}など、レンダーの最中にキーを生成してはいけない。

なぜReactはkeyを必要とするのか

兄弟間で項目を識別するため。並べ替えや削除、追加されたとしてもkeyのおかげでReactは正しく識別することができる。

コンポーネントを純粋に保つ

純粋性:コンポーネントとは数式のようなもの

純関数(pure function)とは以下のような特徴を持つ関数を指す。

  • 自分の仕事に集中する。呼び出される前に存在していたオブジェクトや変数を変更しない。
  • 同じ入力には同じ出力。同じ入力を与えると純関数は同じ結果を返す。
    Reactはすべてのコンポーネントが純関数であると仮定して設計されている。同じ入力をされたら常に同じJSXを返さなければならない。

副作用:意図せぬ(?)付随処理

上記だけだといまいちピンとこないため、公式ページに載っていた悪い例を見ていく。

サンプル
let guest = 0;

function Cup() {
  // Bad: changing a preexisting variable!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

外部で宣言されたguestを読み書きしているせいで、Cupコンポーネントを呼び出すたびに(propsは同じにもかかわらず)違うJSXが返される。
同じ入力には同じ結果が返されなければならないため、正しくは下記のようになる。

サンプル
function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}

ローカルミューテーション:コンポーネントの小さな秘密

上記の例のようにコンポーネントがレンダーの最中に既存の変数を変更してしまうことをミューテーションと呼ぶ。純関数は関数のスコープ外の変数や、呼び出し前に作成されたオブジェクトをミュ―テートしてはならない。
しかしレンダー中に関数内で作成した変数やオブジェクトであれば、書き換えても問題ない。

サンプル
function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaGathering() {
  let cups = [];
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />);
  }
  return cups;
}

上記の例は関数内で作成されたcupspushしている(TeaGathering内で完結している)ため問題ない。これをローカルミューテーションと呼ぶ。

副作用を引き起こせる場所

ではどこで変化させればいいのか。Reactでは副作用(スクリーンの更新、アニメーションの開始、データの変更などの変化)は通常イベントハンドラ内に属する。イベントハンドラはコンポーネント内に定義されてはいるが、レンダーの最中に実行されるわけではない。そのためイベントハンドラは純粋である(純関数である)必要がない。
副作用を書くのに適切なイベントハンドラがJSXにない場合、最終手段としてuseEffectを付加することも可能。

UIをツリーとして理解する

このセクションはアプリが小さいうちはあまり恩恵はないが、大きくなってきた際に真価を発揮しそうな内容である。
正直今はちんぷんかんぷんであるため、後々見返したい。

UIをツリーとして理解する

ツリーとはアイテム間の関係を表すモデルの一種。例えばブラウザはHTMLやCSSをモデル化するためにツリー構造を使用する。モバイルプラットフォームもビューの階層構造を表現するためツリー構造を使用する。Reactも同様にツリー構造を使用して、アプリ内のコンポーネント間の関係を管理しモデル化する。

レンダーツリー

コンポーネントの主要な特徴の一つ、コンポーネント同士を組み合わせること。コンポーネントをネストすることで、親コンポーネント、子コンポーネントという概念が発生する。その親コンポーネントも別のコンポーネントの子であることもある。
Reactアプリをレンダーする際、この関係性をツリーとしてモデル化したものをレンダーツリーと呼ぶ。
練習用に使用しているプロジェクトをレンダーツリーを構築すると下記のようになる。

Home
  └ PageLayout
       ├ MyInput
       ├ MyButton
       └ MyList
            └ MyListItem

ツリー構造はノードで構成されており、各ノード(MyInput, MyButton等)はコンポーネントを表す。
Reactレンダーツリーのルートノードはアプリのルートコンポーネントとなる。上記の例の場合はHomeがルートコンポーネントであり、Reactが最初にレンダーするコンポーネントである。
条件付きレンダーでレンダーツリーが異なることがあるにせよ、このようなツリーは一般的にReactアプリケーションにおいてトップレベルコンポーネントとリーフ(葉, 末端) コンポーネントがどれなのかを理解するのに役立つ。トップレベルコンポーネントとはルートコンポーネントに最も近い(上記ではPageLayout)コンポーネントを指し、下位すべてのコンポーネントのレンダーパフォーマンスに影響を与える。リーフコンポーネントはツリーの下層にあり、子コンポーネントを持たず、通常頻繁に再レンダーされる。
これらを特定することにより、アプリケーションのデータの流れとパフォーマンスの理解に役立つ。

モジュール依存関係ツリー

Reactアプリにおいてもう一つツリー構造でモデル化できるものがアプリのモジュールの依存関係。この場合、枝はimportを表す。
下記は練習用アプリをモジュール関係ツリーにしたもの。これではコンポーネント以外のモジュールを読み込んでいないため、上記のレンダーツリーと全く同じになってしまっているが、本来はレンダーツリーにはコンポーネントのみ、モジュール依存関係ツリーにはコンポーネントを含まないモジュールも含む。

Home
  └ PageLayout
       ├ MyInput
       ├ MyButton
       └ MyList
            └ MyListItem

依存関係ツリーはReactアプリを実行するためどのモジュールが必要か判断するのに役立つ。本番環境用にビルドする際、クライアントに送信するために必要なJavaScriptをすべてバンドルにまとめるビルドステップが存在する。これを担当するツールであるバンドラは、依存関係ツリーを使用することでどのモジュールを含めるべきか決定する。アプリが大きくなるにつれてバンドルサイズも大きくなり、クライアントがダウンロードして実行するコストもかかるようになる。UIが描画されるまでの時間も遅くなる。その際のデバッグとして役立つ。

Discussion