🐕

[初心者向]Reactスターターはここから始めろ!その2

2024/11/08に公開

前提

当記事はJavaScriptもReactもそんなに詳しくないよって人向けです。

↓↓前記事↓↓
https://zenn.dev/kkyoka/articles/759c12ed5ef329

React公式ドキュメントの「Reactを学ぶ」のインタラクティビティ追加のところを読んでいきます。
以下はそれのまとめ。

1. イベントに関すること

書き方の話

  • 要素にイベントを追加する方法
    • イベントハンドラ関数を定義して、JSXタグにpropsの一部としてイベントハンドラ関数を渡す
    • export default function Button() {
          // イベントハンドラ関数の定義
          function handleClick() {
              alert('You clicked me!');
          }
      
          return (
              <button onClick={handleClick}> // イベントハンドラ関数をonClickとして渡している
                  Click me
              </button>
          );
      }
      
  • イベントハンドラ関数の書き方
    • コンポーネントの内部で定義する
    • イベント名の先頭にhandleが付いた名前にする
  • イベントハンドラ関数はインラインで定義したりアロー関数を用いてもOK
    • <button onClick={() => {
          alert('You clicked me!');
      }}>
      
  • イベントハンドラを受け取る用のpropsには自由に名前をつけられる
    • 命名規則名前:頭にonをつける
    • // イベントハンドラを受け取るコンポーネントの定義
      function Button({ onSmash, children }) { // onSmashで受け取っている
          return (
              <button onClick={onSmash}> // onClickにonSmashを渡している
                  {children}
              </button>
          );
      }
      
      export default function App() {
          return (
              <div>
                  <Button onSmash={() => alert('Playing!')}> // onSmashにイベントハンドラ関数を格納している
                      Play Movie
                  </Button>
              </div>
          );
      }
      
  • イベントハンドラ関数は渡すだけ、実行しない
    • // OK
      <button onClick={handleClick}>	
      
      // NG
      <button onClick={handleClick()}>
      
    • JSX内のJavaScriptはレンダー時に実行されるため、{handleClick()}と書くとイベント発火時ではなくレンダー時に実行されてしまう
  • イベントハンドラは適切なHTMLタグに設定する
    • そうでないと、ブラウザ組み込みの振る舞いが有効にならないことがある
    • // OK
      <button onClick={handleClick}> 
      
      // NG
      <div onClick={handleClick}>
      

用語memo

組み込みコンポーネント:Reactで最初から提供されている標準的なコンポーネント

2. stateに関すること

仕組みの話

  • stateとは
    • コンポーネント固有のメモリのこと
    • state変数、stateセッタ関数を持つ
  • useStateでできること
    • レンダー間でデータを保持できる
    • 変数を更新し、Reactがコンポーネントを再度レンダーするようにトリガする
  • なぜstateが必要か
    • ローカル変数はレンダー間で保持されない
      • ローカル変数を更新しても、新しくレンダーすると初期値に戻ってしまう
  • stateはフック(Hook)のひとつ
    • フック(Hook)とは
      • React がレンダーされている間のみ利用可能な関数
      • 関数名はuseで始まる
  • stateはコンポーネント単位で独立している
    • 他コンポーネントで定義されたstateが影響を及ぼすことはない
    • 同じコンポーネントだとしても別場所にレンダーされていた場合、そこで使用されるstate変数は互いに影響しない
  • state変数はイミュータブル
    • state変数の更新はセッタ関数でのみ可能
    • そのためセッタ関数を用いないで値を書き換える行為はできない
    • state変数は読み取り専用として扱う
  • state変数の更新は書き換えではなく置き換え
    • state変数がオブジェクトでも配列でも
    • オブジェクトや配列を更新したい場合は、別のオブジェクト、配列を用意してそれをセッタ関数にセットする必要がある
    • 以下のように直接書き換えられない
      • person.name = 'hoge';
        

書き方の話

  • useStateの書き方
    • const [{state変数名}, set{state変数名}] = useState(0);と書くのが一般的
  • useState()の引数はstate変数の初期値
  • stateセッタ関数の引数の種類
    • 直接値を渡す形式のセッタ関数
      • 例:setNumber(number + 1)
      • numberの値が0の場合、「numberを1に置き換えてね」の意味となるので、何回実行してもnumberは1
      • setNumber(n => 1)と同義
    • 関数を渡す形式のセッタ関数
      • 例:setNumber(n => n + 1)
      • numberの値が0の場合、「numberに1を足してね」の意味となるので、実行回数分+1される
      • レンダー中に実行されるので注意
  • state変数にはオブジェクトも格納できる
  • オブジェクトのstate変数を更新する際に、一部のプロパティのみ更新したい場合
    • ...を使う
    • const [person, setPerson] = useState({
          firstName: 'Barbara',
          lastName: 'Hepworth',
          email: 'bhepworth@sculpture.com'
      });
      
      setPerson({
          ...person, // firstName以外の項目はそのまま
          firstName: 'hoge' // firstNameだけ更新
      });
      
  • オブジェクトのstate変数を更新する際に、プロパティ名を動的にしたい場合
    • []で囲むとプロパティ名を動的に指定できる
    • function handleChange(e) {
          setPerson({
             ...person,
             [e.target.name]: e.target.value // e.target.nameという動的なプロパティ名
         });
      }
      
  • オブジェクト内で別オブジェクトを参照している場合も書き方は同様
    • setPerson({
          ...person, // personオブジェクト自体はすべてコピー
          address: {
              ...person.address, // personオブジェクトのaddressのprefecture以外はコピー
              prefecture: 'Hiroshima' // prefectureのみ更新
          }
      });
      
  • 上記の書き方が煩わしい場合はImmerライブラリを使うと良い
    • npm install use-immer実行で依存ライブラリ追加できる
  • フックはコンポーネントのトップレベルまたはカスタムフック内でしか呼び出せない
    • const [hoge, setHoge] = useState();はコンポーネントの一番最初に書く必要がある
    • if文やfor文内で呼び出しはNG
    • フックは同一コンポーネントの各レンダー間で同一の順番で呼び出されることに依存しているため
      • レンダーするたびにフックの呼び出し順序が変わってしまうと、どのstate変数を返すべきか判断できなくなってしまう...

3. stateとレンダーに関すること

仕組みの話

  • レンダーとは:Reactがコンポーネントを呼び出すこと
  • レンダーは以下をトリガにして処理が開始される
    • 初回画面描画時
    • state変数の更新時
  • 画面描画までの流れ
    • 初回描画時
      1. レンダー処理(ルートコンポーネント呼び出し)
      2. DOMノードの更新
      3. ブラウザレンダ
    • 再描画時
      1. レンダー処理(state変数の更新によってトリガされたコンポーネント関数の呼び出し)
      2. 初回表示時と異なるDOMのみを更新
      3. ブラウザレンダ
  • コンポーネントをレンダーするたびにuseState()が動く
    • export default function Example() {
          const [index, setIndex] = useState(0); // ここがレンダーのたびに動く
          
          function handleClick() {
              setIndex(index + 1);
          }
      
          return (
          <>
              <button onClick={handleClick}>ボタン</button>
          </>
      }
      
    • 1回目レンダー
      • useState(0)の戻り値は[index=0, setIndex]
    • ボタン押下
      • handleClickが動き、index=1に更新される
      • state変数の更新をトリガに2回目レンダーが動く
    • 2回目レンダー
      • useState(0)の戻り値は[index=1, setIndex]
      • Reactは更新されたstate変数の値を覚えているため、indexを0ではなく1としてみなす
  • state変数の値が実際に変更されるのは次回のレンダー時
    • 同じレンダー間でstateセッタ関数を何度呼び出しても、実際にstate変数の値が変更されるのは次のレンダー時
    • 以下の場合、ひとつのイベントハンドラ関数内でsetNumber()が3回呼ばれているが、numberは次回レンダーが走らない限り値は同じ(初回であれば常に0)なのでボタン一回クリックで1しか加算されない
    • import { useState } from 'react';
      
      export default function Counter() {
          const [number, setNumber] = useState(0);
          return (
              <>
                  <h1>{number}</h1>
                  <button onClick={() => {
                      setNumber(number + 1);
                      setNumber(number + 1);
                      setNumber(number + 1);
                  }}>ボタン</button>
              </>
          )
      }
      
  • stateセッタ関数が複数呼び出される場合、何度もレンダーが走るのか
    • 該当のイベントハンドラの処理が完了するまで、state変数の更新は待機される
    • イベントハンドラの処理の完了後、state変数が更新されレンダーが走る
      • stateセッタ関数はキューに積まれ、イベントハンドラの処理が完了した後に順次実行される
    • バッチ処理と言われる
  • stateセッタ関数が、「関数を渡す形式」の場合、その関数はレンダー中に実行される

4. JavaScriptに関すること

  • イベントハンドラは子要素のイベントであってもキャッチする
    • 子要素のイベントをキャッチした際、まず子要素のイベントハンドラが呼び出されその次に親要素のイベントハンドラが呼び出される(階層数に限りなし)
    • 以下でボタン1を押下した際、hoge2、hoge1の順番で表示される
    • export default function Example() {
          return (
              <div onClick={() => {alert('hoge1');}}> // 親要素
                  <button onClick={() => alert('hoge2')}>ボタン1</button> // 子要素
              </div>
          );
      }
      
  • イベントハンドラはイベントオブジェクトのみ引数として受け取れる
    • 通常eという変数で定義される
  • イベントの伝播を止めたい場合はe.stopPropagation()を呼び出す
    • ツリーの上側にあるタグ(親要素およびその上の要素)にアタッチされたイベントハンドラが発火しないようにできる
    • ただしonScrollイベントは元々伝播しないようになっている
  • ブラウザデフォルトイベントを止めたい場合はe.preventDefault()を呼び出す
  • createRoot()root.render()によって初回レンダーされる
    • const root = createRoot(document.getElementById('root'))
      root.render(<div>hogehoge<div/>);
      
  • 配列のstate変数を置き換える際に便利なメソッド(state変数は書き換えではなく置き換えが必要なので、以下を活用する必要がある)
    • concat:要素を追加した配列を返す
    • filter:要素を条件に一致したものだけにした配列を返す
    • slice:配列の一部を取り出した配列を返す
    • map:要素を別の値に置換した配列を返す
    • pushspliceなどのメソッドは直接配列を更新してしまうので使えない
  • コピーした配列でも、参照先は同じ
    • 以下の場合、nextList[0]を書き換えるとlist[0]も同様に書き換えられる
    • person1 = {
          name: 'hoge1',
          age: 25
      };
      person2 = {
          name: 'hoge2',
          age: 30
      };
      person3 = {
          name: 'hoge3',
          age: 18
      };
      list = [person1, person2, person3]; 
      const nextList = [...list]; // listをnextListにコピー
      nextList[0].name = 'fuge'; // person1.nameが'fuge'に書き換えられる
      alert(list[0].name); // fugeで表示される
      
    • そのためstate変数が配列の場合、「元の配列をコピーして、コピーした配列の要素を変更し、その配列をセットする」やり方もNG
      • const [list, setList] = useState([
            person1,
            person2,
            person3
        ]);
        const nextList = [...list]; // listをnextListにコピー
        // person1.nameも書き換えられてしまうので、 state変数を直接書き換えてしまっていることになる
        nextList[0].name = 'fuge';
        setList(nextList);
        
    • 「元の配列をコピーして、コピーした配列の要素をならびかえ、その配列をセットする」やり方はOK(要素を更新しているわけで)
      • const nextList = [...list]; // listをnextListにコピー
        nextList.reverse(); // nextListを並び替え
        setList(nextList); // nextListをセット
        

用語メモ

  • バブリング (bubble),伝播 (propagate)する:子要素のイベントをキャッチして、親要素のイベントハンドラが呼び出されること
  • ブラウザデフォルトイベント:formタグの中にあるbuttonタグをクリックすると自動でフォームが送信されるようになっているイベント、などデフォルトで設定してあるイベント

感想

Reactの公式ドキュメント、丁寧で分かりやすい。

参照

Discussion