Closed6

React のドキュメントを読む① - UIの記述

ShionShion

初めてのコンポーネント

  • React コンポーネントとは、マークアップを添えることができる JavaScript 関数
  • React アプリケーションは “ルート (root)” コンポーネントから始まる(自動的に生成)
  • React アプリでは隅から隅までコンポーネントが使われる
ShionShion

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

  • コンテンツは HTML で書き、デザインは CSS で書き、ロジックは JavaScript で書き、それらを別ファイルにしていた
  • React では JSX でロジックとマークアップを同じ場所に書くことができる
  • JSX とは JavaScript の拡張であり、JavaScript ファイル内に HTML のようなマークアップを書けるようにするもの
  • JSX タグが複数あるときにラップしないといけない理由
    • JSX の実態は JavaScript である
    • JavaScript で複数のオブジェクトを返す際に配列でラップしなくてはいけないのと同様、JSX でも複数のタグを返す際は別タグがフラグメントでラップする必要がある
  • React では多くの HTML および SVG の属性はキャメルケースでかく
    • JSX は JavaScript に変換され、中に書かれた属性は JavaScript オブジェクトのキーになる
    • JavaScript の変数名には一定の制約がある(例えば、名前にハイフンを含めたり class のような予約語を使ったりはできない)
    • そのため class は className というふうにキャメルケースで書く必要がある

JSX に波括弧で JavaScript を含める

  • 以下のような{{ と }} は、JSX の波括弧の中に書かれたオブジェクトに過ぎない
    • 前述したように属性はキャメルケースで書く
<ul style={
  {
    backgroundColor: 'black',
    color: 'pink'
  }
}>
ShionShion

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

  • <Avatar {...props} /> のような JSX スプレッド構文ですべての props を転送できる
  • props とはある時点での読み取り専用のスナップショットで、書き換えることはできない(インタラクティブ性が必要な場合は state を設定)
ShionShion

リストのレンダー

  • map() 内で直接 JSX 要素を使用する場合、必ず key が必要
  • key は動的に生成するのではなく、元データに含めるべき
    • 兄弟間で項目を一意に識別できるようにするために key が必要
    • インデックスや Math.random() で key を渡してはならない
    • インデックスだと要素が削除されたり並び替えたりするとレンダーする順番が変わってしまうため(ややこしいバグになる)
    • Math.random() だとレンダーするたびに変わってしまうため
    • 安定したIDを key に渡すようにする
    • データベースからのデータや uuid などのパッケージから生成された値
  • <>...</> フラグメント構文では key を渡せないため、これらを 1 つの <div> にグループ化するか、やや長くてより明示的な <Fragment> 構文を使用する
const listItems = people.map(person =>
  <Fragment key={person.id}>
    <h1>{person.name}</h1>
    <p>{person.bio}</p>
  </Fragment>
);
ShionShion

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

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

  • 純粋性(純関数)とは以下の特徴を持つ
    • 自分の仕事に集中する - 呼び出される前に存在していたオブジェクトや変数を変更しない
    • 同じ入力には同じ出力 - 同じ入力を与えると、純関数は常に同じ結果を返す
  • Reactコンポーネントも純関数であるべきである
    • つまり、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 変数を読み書きしているため、このコンポーネントを複数回呼び出すと、異なる JSX が生成される
    • さらに悪いことに、ほかのコンポーネントも guest を読み取る場合、それらもレンダーされたタイミングによって異なる JSX を生成することになる(予測不可能)

修正ver

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} />
    </>
  );
}

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

  • ただし、ローカルミューテーションは問題ない
    • 上記の例ではコンポーネント外の変数の値を書き換えていたが、コンポーネント内で書き換えるのは問題ない
export default function TeaGathering() {
  let cups = [];
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />);
  }
  return cups;
}
  • この例では、cups 変数と [] 配列は、TeaGathering 内で同一のレンダー中に作成されたものであるため、問題ない
  • TeaGathering 以外のコードは、これが起こったことすら知るすべがないからである
  • これは **ローカルミューテーション (local mutation)”**と呼ばれる
  • コンポーネント内のちょっとした秘密のようなもの

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

  • 純粋性が重要であるとはいえ、いつか、どこかの場所で、何らかのものが変化しうる
  • これらの変化(スクリーンの更新、アニメーションの開始、データの変更など)は 副作用 (side effect) と呼ばれる
  • React では、副作用は通常、イベントハンドラの中に属する
  • イベントハンドラは、コンポーネントの「内側」で定義されているものではあるが、レンダーの「最中」に実行されるわけではない
  • つまり、イベントハンドラは純粋である必要はない
  • いろいろ探しても副作用を書くのに適切なイベントハンドラがどうしても見つからない場合は、コンポーネントから返された JSX に useEffect 呼び出しを付加することで副作用を付随させることも可能
  • これにより React に、その関数をレンダーの後(その時点なら副作用が許される)で呼ぶように指示できる
  • ただしこれは最終手段であるべき
ShionShion

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

レンダーツリー

  • レンダーツリーの概念は、トップレベルとリーフコンポーネントを特定するのに役立つ
  • トップレベルのコンポーネントはそれらの下の全コンポーネントのレンダーパフォーマンスに影響を与え、リーフコンポーネントは頻繁に再レンダーされる
  • これらを把握することでレンダーパフォーマンスの理解とデバッグに役立つ

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

  • React アプリを本番環境用にビルドする際には、クライアントに送信するために必要な JavaScript をすべてバンドルにまとめるというビルドステップが存在する
  • これを担当するツールはバンドラ(webpack, viteなど)と呼ばれる
  • アプリが成長するにつれて、バンドルサイズも大きくなる
  • バンドルサイズが大きいと、クライアントがダウンロードして実行するのにコストがかかる
  • バンドルサイズが大きいと、UI が描画されるまでの時間も遅くなる
このスクラップは2024/03/10にクローズされました