2024/01/27

 前回記事同様、「TypeScriptとReact/Next.jsでつくる実践webアプリケーション開発」の学習記録。

 今回からchapter3のReactの章へ移行。既に別の本である程度はやっているが、TSやReactの型など分からないため最初からやっている。

 

 ReactはJSXという記法でJS、TSファイルの中に直感的にHTMLタグを書けるようなやつ。JSXで作られたReact要素(UI)に振る舞い方や挙動などを抱き合わせるとコンポーネントという単位にもなる。
 コンポーネントは関数のように宣言される関数コンポーネント、クラスのように宣言されるクラスコンポーネントがあり、今は関数コンポーネントが主流のようだ(筆者が以前学習した技術書でもそうだった)

 

 コンポーネントとpropsと型

 コンポーネントへは任意のオブジェクトをデータとして渡すことができる。それらはpropsの中に格納され、propsに変数名でアクセスできる。いちいちpropsにアクセスするのも面倒なので、分割代入でいったん取り出したほうが楽だろう。

 また、コンポーネントの開始タグと終了タグの間にはさまったコンポーネントはchildrenへと格納されるようだ。

 コンポーネントの受け取るオブジェクトにも型をつけられるし、おそらくは返す値にも型をつけられるはずだが、まだ現時点では返す値や詳しいReactの型に関してはやっていない。

 ただ、関数コンポーネントの性質として、JSX.Element型の値を返すらしい。childrenとして受け取るコンポーネントの型はReact.Reactnode型という広範な型らしいが……詳しくは注釈を読むとしよう。

 以前はFC型やVFC型などもあったようだが、今は下火のようだ。

 

 Context

 propsでデータを親から子へ渡すのは良いが、いちいち上から下へと渡すのは苦労する。そこで使うのがContext。

 Contextコンポーネントを作り、それに値を預け、必要な時に取り出すというもの。

 Contextコンポーネントを作ったあとは、Context.Providerと呼びだしてやることで、任意の変数で任意の値を渡せる。
 取り出す際は取り出したいコンポーネントの親として、Context.Consumerと囲んでやれば取り出せる。

 Contextオブジェクトは以前もそうだったが取り出し方などで躓いたのでまた後で復習する。

 

 React Hooks

 React Hooks……フックによって関数コンポーネント内で値を管理、変化させることが可能になった。公式で提供されているものは全15種類あり、それらを組み合わせてオリジナルのフックを作ることも可能。

 今日やったのはuseState、useReducer、useCallbackの3種。

 

 useState

 名前の通り、状態(ステート)を扱うフック。useStateを関数呼び出しをし、配列に[状態変数, 更新関数]と分割代入をすることで、データとそれを更新・管理する関数が別個で取り出せる。useState呼び出しの引数は状態の初期値となる。

 更新関数の戻り値が新しい状態変数へと収まる。シンプルに数値をインクリメントするなどにも使える。

 本書をやっていてびっくりしたのは、更新関数にコールバック関数を渡した場合の挙動。そうした場合、仮引数に勝手に状態変数が代入されて処理が行われる。

 複数仮引数を記述した場合は分からないが、手間が省ける記法だなぁと感心した。

 

 useReducer

 状態を扱うフックなのはuseStateと変わらないが、名前の通りArrayオブジェクトのreduceメソッドのような感じ。オブジェクト、配列などの複数のデータを集めたものの管理が得意。

 useStateとほとんど変わらないが、useReducerの引数が1つではなく2つに増えるのが違うところ。1つ目にreducerと呼ばれる更新関数を渡し、2つ目にreducerに渡される初期状態が入るところ。

 更に、reducer自身も2つの仮引数を必要とし、一つ目は現在の状態変数、二つ目はactionと呼ばれるデータが入る。状態変数とactionを元に処理を行い、返した値が次の状態となる。

 あくまでreducerを介した更新であり、つまりは現在の状態とactionという2つのデータを必要とし、それらをさばいていくフック。

 本書では状態変数で数値のカウントをし、actionではどのボタンが押されたかによってインクリメント、デクリメントなどカウントを操作するというswitch文でのテストコートがあった。

 

 useCallbackとmemo

 useCallbackとuseMemoはどちらも値や関数を保持(メモ化)し、不必要なレンダリングを避けるためのフック。だが、今日ははmemoとuseCallbackに留まっているので後日useMemoを追記する。

 Reactはコンポーネントが再描画されるタイミングはいくつかあるが、中でも注意しなければならないのは「親コンポーネントが再描画された時」。

 ある親コンポーネントが再描画されると、それだけで下にある子以下のコンポーネントは再描画される。そうやって無駄なレンダリングを避けるために使われる。

 

 memoは、関数の頭につけてやると簡単にその関数をメモ化できる。
 関数コンポーネントのサンプルコードではジェネリック型が続いているのが見受けられたが、付けられるだけなのか必須なのかは分からない。後日調べよう。

 これによって親が再描画されても「propsが変化しない限りは同じものを返し続ける」コンポーネントが誕生する。

 ただし、渡されるpropsが親の再描画によって新規生成されると意味がなさそうだ。親から子へ何かのデータが渡されていて、それが親コンポーネントの中で生成されているものだとしよう。

 親が再描画される=また全部処理をやり直しなので、結局データも再生成される。ということはメモ化が意味をなさない。「新しいデータがきたから自分も再描画しないと!」と、せっかくメモ化したはずのコンポーネントが再描画される。

 

 またメモ化フックにuseCallbackがあるが、上記のmemoよりもより厳しい判定のメモ化ができる。こちらは名前の通りコールバック関数を受け取り、それを保持する。

 useCalbackは第一引数に関数を、第二引数に依存配列と呼ばれる配列を受け取る。

 上記のmemoと違い上から渡されるpropsではなく、依存配列に変化がない限りは同じ関数を返す。親から渡されるpropsだろうと、Contextの値だろうと、依存配列に関係がなければ変化は起きない(新しく関数を生成しない)。

 空の配列を渡すこともでき、これはReactアプリの初回レンダリング時以降同じとして扱われ、二度と関数を生成はしない。もちろんF5キーなどで全体を更新した場合には違うけども。

 

 今日はだいぶ手が遅かったのと、メモ化フック両方やるつもりだったが果たせなかった。明日リベンジ。