Skip to content

Latest commit

 

History

History
596 lines (334 loc) · 50.3 KB

chap-erukiti-react.md

File metadata and controls

596 lines (334 loc) · 50.3 KB

ハンバーガー屋さんで隣の女子高生がReactについて語っていました

今回の合同誌の2つめの章になりますが、これはもともと、「OLの恋バナから学ぶSaaSのセールス&マーケティング用語」という弊社社員が書いた記事がめちゃくちゃ秀逸でマジリスペクトだったので、書いてみたものです。

というかReactの解説を元記事ほど軽妙に書くのは難しいですね。でもいつかはエンジニアリングの言葉を、こういうわかりやすく軽妙な記事にしてみたい!

ちなみに、Noteに書いたこれらの記事をベースに加筆修正したものです。

第一話 Reactとは

R子「最近さー、うちReact書いてんねん」

 Reactは、Facebook社が開発しているウェブブラウザ向けのユーザーインターフェースライブラリ。同種のモノにVueやAngularなどがあるが、現時点で世界シェアトップはReactである。

J美「まじでー?jQueryじゃだめなん?」

 jQueryは、ウェブブラウザ戦争が激しかった頃に、ブラウザ間の互換性を取り持つことでシェアを拡大したライブラリ。昔としてはとても便利だった。プラグイン含め様々な情報やソースコード資源がある。

R子「ペライチなウェブサイト制作するならええけどー。真面目に開発しよー思たら、jQueryは治安悪すぎてしんどいよー」

 ここでいうペライチとは固有のサービスのことではなく、ペラ一枚、つまり単一ページか、それの組み合わせ程度で成り立つ昔ながらのウェブ制作のこと。ウェブというとこちらを想像する人が多い。単価は安くても案件としては多い(はず)

 治安とは、ソースコードがとっちらかったり、ルール無用であらゆる書き方やプラグインやライブラリやコピペなどが横行している状態。「ソースコードの治安が悪い」などと表現される。

J美「Reactって仮想DOMだっけ?あれってなんなん?魂震えるん?」

 仮想DOMは、ウェブブラウザの画面描画では、DOMの更新が動作を重くする原因であるため、Reactを使うプログラマはDOMを直接操作せずに、Reactのコンポーネント及びDOMのサブセットのようなものを操作する。Reactはこれらの操作を元に、最終的にDOMの書き換えを行う。賢いヤツ

 DOMは、Document Object Modelの略で、HTMLをJavaScriptから扱う時にアクセスするデータモデルだと思えばOK。

R子「うちらがやりたいのはDOM操作やのーて、ウェブブラウザを使ったアプリ開発やからなー。ReactみたいなSPAライブラリが流行ったのと、ブラウザ戦争が終結してくれたおかげで、めっちゃ楽になったんよー」

 DOM操作が主目的ならjQueryでもなんでもいいと思う。

 SPAはSingle Page Actionの略で、ページ遷移せずに画面が切り替わる技術のこと。これのおかげで、ウェブブラウザ上で、ネイティブアプリに近い操作感が得られるようになった。

 ブラウザ戦争があった頃は、各社が好き勝手やってたせいで、5年経っても仕様が固まらないなどといった停滞があった。

J美「DX改革だね」

R子「そうそう。今はもうウェブ開発ってVSCodeと優秀なライブラリのおかで、DXめっちゃアガってんねんよー」

 DXはここでは Developers eXperience 開発者体験の方。実際にここ数年でウェブフロントエンドを中心に、開発の快適さが急激に上昇している。特にMicrosoft社のOSSなIDEであるVSCodeは並のテキストエディタよりも軽量なのに、IDEとしての機能も十分で、ウェブ開発に最適である。最近はJavaなんかもサポートを強化している。

J美「最近やったらVueとかもいいんじゃないの?」

R子「ええーと思うよー。世界的なシェアは圧倒的にReactやけど、伸びてるのは間違いないでー。ウチはJSXがあるからReactの方が好きやけどねー」

 VueもReactみたいなやつ(語弊がある)Reactよりは高機能かつ、公式エコシステムが豊富な分、Reactよりはとっつきやすいと言われている。

 JSXは、JavaScript/TypeScriptのソースコードの中にHTMLタグのようなものを記述できる拡張。

J美「JSXってどうなん?あたしはHTMLテンプレート使う方がなじみやすいと思うんだけど」

R子「JSXは考え方がHTMLテンプレートとは逆やねんよー。HTMLやDOMってツリー状のデータ構造やん?そういうデータを扱うのは、プログラミング言語が元から普通にやってることやねんから、テンプレートっていう考え方でいちいち分けるよりは、プログラミングの一工程に混ぜ込む方がシンプルやねー」

 HTMLテンプレートの考え方はとても古くからあり、JSPやRailsなど多くのフレームワークで採用されている、おなじみのもの。HTMLの中に特殊な記法で変数名や、限定的なプログラムコード(DSLという)を書く。

 ツリー状のデータ構造とは、根っこ(root)から、幹が伸びて枝が広がって葉っぱがあるような、データ構造のこと。プログラミングでは標準的に使われる。

J美「HTMLテンプレートって分かりやすいと思うけど、JSXの利点ってプログラミングの工程に混ぜ込めることなの?」

R子「プログラミングの工程に混ぜ込むだけやと、あんまり利点わからんかもやねー。真価を発揮するのはリファクタリングとかやでー。HTMLテンプレートでリファクタリングするのはどうしても限界あるしなー」

 リファクタリングとは、「外からみた振る舞いを変更せずに、中身を書き換える」こと。外から見た振る舞いが変わってしまったら、それは決してリファクタリングではないことに要注意。

J美「リファクタリングってなんで必要なの?」

R子「プログラムを最初から完璧な状態で書くのが無理やからやでー。いつでも、慣れた同じものを作れるとは限らんし、そもそもビジネスの変化早い時代やねんから、一ヶ月で異なる要件出てくることもザラやし、実際のモノを作ったら、クライアントがコレジャナイって言い出すこともあるしねー」

J美「確かにプロトタイピングって重要だよね」

 プロトタイピングは人によって違うものを指す場合も多いが、中身は完成してなくても最小限の動きを実装し、なるべく実際のアプリケーションに近いものを作ることで、プロダクトについて判断できる人や企画者に動きを確認してもらう。

R子「リーンでいうMVP(仮説を検証する最小限のプロダクト)を短いサイクルでリリースして、仮説を立てて市場を伺いつつ検証するっていう考え方も当たり前になってきてるしなー。短いサイクルで効率的に開発しよう思たら、がっちり最初からキチキチに開発するよりはリファクタリングできるような開発を最初からしておいた方が後々楽なんやわー」

 リーン開発手法、大本はトヨタの生産手法に源流を置く、世界的な、ソフトウェア開発手法のこと。

 原則1:ムダをなくす

 原則2:品質を作り込む

 原則3:知識を作り出す

 原則4:決定を遅らせる

 原則5:速く提供する

 原則6:人を尊重する

 原則7:全体を最適化する

J美「HTMlテンプレートだとリファクタリングしづらい?」

R子「せやねー。HTMLテンプレートって、もともとの目的が非プログラマに触らせるためのもんやから、リファクタリングはほとんど考えられてないからなー。使い捨てならええけど、リファクタリングやるんなら、ユニットテストとかもある、ホンモノのプログラミング言語の方が絶対楽なんよー」

 ユニットテストは、単一機能を持った関数・メソッドなどを自動テストするための軽量な仕組み。JavaのJUnitやJavaScriptのJestなどがある。

 ホンモノのプログラミング言語じゃない環境で何か工夫しようとすると大体破綻する。

第二話 データの持ち方

J美「R子がReactやってるっていうから触ってみたんだけど、Flux?Redux?って何なの?」

R子「まず、Fluxからいくでー。FluxはReactに適したデータフローのアーキテクチャやねー。F8っていうFacebook社の技術イベントで提唱されたものやねん。MVCとかMVVMが抱えていた根本的な複雑性を解決する冴えたやり方やねー。」

 データフローは、データの流れる向きや順序のこと。Reactのようなデータドリブン・イベントドリブンなプログラミングでは、データの流れをつかむことで、プログラム全体を理解しやすくなる。

 アーキテクチャは、広い意味をもつ言葉だが、ここでは大きい枠組みくらいに捉えておくと吉。

J美「MVCやMVVMって何が問題なん?MVCとかってめっちゃ昔から使われ続けてるよね?」

R子「Model と View と Controller で構成されてるのがMVCやけど、それぞれの依存関係が問題やねん。相互依存とか、多対多の依存関係になりがちやねんよー。MVCって半世紀以上前から使われ続けてるだけあってさまざまな亜種生み出してるけど、それらの歴史の中で、依存関係の問題に踏み込んで解決まで持って行ったの皆無やねんよー」

J美「依存関係が複雑ってこと?」

R子「せやねー。Fluxではデータフローを決めることで依存関係が複雑にならないようにしたんよー」

 ソースコードは何かしらどこかに依存するか、依存されている。このときの依存とは、呼び出されてるとか、ソースコードで定義された情報を参照されているなどを意味する。

 相互依存とはお互いが依存しあう関係のこと。もっとも結合度が高いためソースコード上で分けて考えづらくなる。

 多対多の依存関係とは、ある Model が複数の View と結びつき、View が複数の Model と結びつくなどという関係のこと。当然ながらこれも結合度は高い。

R子「Fluxのコツはデータの流れを一方通行にすることと、受け口が一箇所に定まってることやなー

J美「データの流れが一方通行ってどういうこと?」

R子「MVCとか既存のアーキテクチャやと、それぞれの役割のコンポーネントが相互に参照してデータを投げたりできるけど、Fluxではそれぞれのコンポーネントは、好き勝手にデータを投げたらあかんねんよー」

J美「つまり、MVCやその系譜では、依存関係やデータの流れについてのルールが無かったけど、Fluxではそこにルールを決めたってこと?」

R子「せやねー。プログラムの複雑さとか技術的負債って大体は、結合度と凝集性の2つが原因やねんよー。今回は不適切な結合度、つまり密結合をなくすためのものやねー」

 凝集性はもう1つの重要な指針だが、ここではいったん置いておく。

J美「結合度についてもうちょっと詳しく教えてよ」

R子「結合度は、密接に結合してる状態を密結合、そうじゃなくてお互いが疎になってるのが疎結合。密結合って、個々のソースコードが密接に関係してるため、切り離して考えられない状態やねんよー。スパゲティコードとかクソ設計とかの原因は大体密結合にあるんよー」

J美「疎結合な状態っていうのは、つまりお互いに参照がほとんどないってこと?参照しないとプログラムって書けないよね」

R子「最小限に抑えた状態やねー。不要な参照を無くす、余計な情報をそもそも出さないとか」

J美「ふーん、なんとなく分かったような気がするから、もう少し具体的に教えて?」

R子「UI作るときの最前線はもちろん View やけど、Flux での View は自分で状態更新をしたらあかんねん。できるのんは、View のイベント(マウスクリックとかキーを押したとか)に応じて、アクションっていうプレインオブジェクトを、ディスパッチャに投げることだけやねん。Propsで渡ってきたコールバック関数を叩くのんは大丈夫やねんけどね」

 プレインオブジェクトは、JavaだとPOJOともいわれるもの。JavaScriptだとJSONというと分かりやすいか。要するに基本的な最小限のデータ(文字列、数字、真偽値、配列、オブジェクトなどで構成される、単純なデータ構造)

 ディスパッチャとは、その関数を実行することで、決められた1つの場所にデータを渡すことができるという、交通整理できる優れた便利関数。

 もともとReactの考え方で、引数で渡ってきた関数を叩くことでツリーの根っこに近い方で状態管理できるようにするというものだった。よく props のバケツリレーなどといわれるもの。

R子「非同期処理なんかも、API通信の結果でダイレクトにビューの更新をするわけやなくて、同じようにディスパッチャに投げるんよー。Redux の場合は非同期処理をするためのミドルウェアを使ったりするねー」

 非同期ミドルウェアとは、React + Redux だけではAPIアクセスなどの非同期処理が綺麗にさばけないため使われるもの。Reduxは、とても簡単にミドルウェアを追加できるため、様々なミドルウェアが存在する。redux-saga や redux-thunk などが有名。

J美「つまり直接データを書き換えるんじゃなくて、アクションっていう、データを書き換えるためのデータを作って、ディスパッチャに投げるっていう間接操作でやるってこと?なんでそんな面倒なことするん?」

R子「データを直接書き換えると、データの書き換え方のルールを決められへんやろ?たとえば、データ作成の時間情報を更新するときに、Date型を入れたり、UNIX time入れたり、UNIX timeのミリ秒入れたり、なんでも出来てしまうやん?」

J美「でもそれだけなら、データモデルをTypeScriptで記述すれば型で制約かけるんじゃない?」

R子「UNIX timeの秒とミリ秒とかだと、TypeScriptでは同じモノに見えてまうからなー。でもディスパッチャ経由にして、ワンクッションおけばユニットテストなんかも書きやすくなるやろー」

 Date型は、JavaScriptで日時を表現するオブジェクト。機能が乏しく操作が面倒なため、度々momentみたいな便利ライブラリが使われる。

 UNIX time (epoch エポックとも言う)は、1970年1月1日0時0分からの通算時刻を秒で表現したもの。ただし、JavaScriptでは秒単位ではなくミリ秒単位で表現したものが主に使われる。そのため、APIなどで秒単位のUNIX time を取得したときには、JavaScript向けにミリ秒やDate型に、データ変換をすることになる。

 TypeScript は、Microsoft社が開発するJavaScriptの完全上位互換かつ、静的型付けをサポートしたハイブリッド言語。最近のJavaScript開発では半分以上のシェアを占めている事実上の標準。

J美「ユニットテストはどういう風に書くん?」

R子「たとえば、アクションを生成するアクションクリエイターのテストやなー。APIの結果から、データモデルを変更するためのミリ秒の数値を生成できるかどうかテストしたりとかやなー。」

J美「なんかまた登場人物増えたね。アクションクリエイタは関数?」

R子「せやねー。ビューのイベントや、非同期処理の結果を、アクションクリエータに食わせて、アクションの生成やディスパッチをするんや。登場人物は確かに増えるけど、通り道が限定されるから、ユニットテストが書きやすくなるし、バグに対処しやすくなるよー」

 実際にはここらへんはバランス感覚が重要なところ。通り道が限定されるのがいいのか?レイヤーが増えることで複雑に感じるか?ユニットテストをどれくらい重視するのか?など、アーキテクトの腕の見せ所である。

J美「関数型プログラミング的な考え方ってやつ?」

R子「せやねー。コンピュータの仕組みまで遡ると、プログラムは基本的にはデータ変換がお仕事やからねー。APIやビューのデータを、アクションに変換する関数って考えると、シンプルかつ堅牢なものにできるやろー」

 関数型プログラミング、ここでは基本的に純粋関数と呼ばれる、入力が同一なら出力が必ず同一になるもの、かつ副作用(わかりやすいものはI/O)を持たないもので、アクションクリエイターは作成可能。そもそもやってることシンプルなんだし。

J美「アクションやディスパッチは分かったけど、ディスパッチされたアクションはどうなるん?」

R子「ディスパッチャーは配送屋さんやね。ディスパッチされたアクションは、ストアに配送されんねんよ。」

R子「Reduxやったら、ストアにはReducer(リデューサ)っていうユーザーが登録するコールバック関数があって、リデューサはアクションに応じてデータモデルを変更して、ストアのもつデータが書き換わるんよー」

 ストアは、データを貯めるところであり、かつ登録されたリデューサによってデータの変換の過程を保証する装置。

J美「ストアがデータを書き換えたとして、そのあとどうなんの?それだけだとビューの状態変わんないでしょ?」

R子「大前提として、ビューに渡るデータは全部ストアが管理しとんねんよー。初期状態もリデューサとストアが受けもつねんけどね」

R子「デザインパターンでいうObserverパターンやねんけど、ストアでデータが更新されたら、ビュー、つまりReactコンポーネントに渡すデータ、つまりPropsを書き換えるんやわー」

 Observerパターンは、デザインパターンの一種で、値が更新したり、なにがしかのイベントに応じて登録したコールバック関数を実行するというもの。イベントドリブンプログラミングやGUIなどでは当たり前のように使われるパターンでもある。

J美「なるほど。ビューのイベントハンドラや、非同期処理の結果は、アクションクリエイターが作ったアクションをディスパッチして、ストアはディスパッチされたアクションでデータを更新する。そうしたらストアはReactコンポーネントに渡すデータを更新することで、ビューの状態が更新されるんだね。疎結合にはなるかもしれないけど、なんかめんどくさくない?」

R子「ぶっちゃけ構造全体を見ると面倒くさいというか大げさに見えるけど、MVCやMVVMよりは交通整理されてるだけマシやと思うわ。あと、ここまで説明してなんやねんけど、今のReactは、Flux/Reduxより先の世界に行っとるから、もっとシンプルになってんねんよ」

第3話 React Hooksの誕生

J美「どんな風にシンプルになったん?」

R子「元々Reactが生まれたときって、Reactコンポーネントは自前の状態(ステート)を持ちつつ、ツリーの根っこから渡されるハンドラによって、自分の状態更新を自分の親やそのさらに親に伝えとってんよー。ただ、そんときは当然、MVVMとかと変わらん複雑さがあったわけやなー」

 Reactは元々、コンポーネントに渡された引数によって仮想DOMを操作し、渡されたハンドラによって状態を更新するという考えで作られていて、それがまさにリアクティブプログラミングだったためにReactという名称がつけらている

 リアクティブプログラミングはたとえば、Excelなんかがそうだが、あるセルの値が書き換わると、それに応じて他のセルも変更される。Reactでも、あるコンポーネントの引数やイベントによって他の状態も変化するという構造である。

J美「Flux/Reduxがデータの交通整理を他が引き受けてくれたから、Reactの構造自体がそこまでややこしいことをしなくて済むようになったってこと?」

R子「せやねー。Reactが当初考えてたよりもシンプルにやってもいいってみんな気づいたんよー。クラス型コンポーネントは複雑だけど高機能やねんけど、そういう理由で、途中から関数型コンポーネントっていう、純粋関数を使うようになったわけやなー」

 クラス型コンポーネントは、昔のReactコンポーネントの作成方法で、React.Component を継承したクラスのこと。じつはさらにもう1つ前のやり方もあったがそれはもう誰も覚えてないと思うので省略する。

 関数型コンポーネントは、単一の関数がコンポーネントになるというもの。

J美「さっきから純粋関数ってよく出てくるけど、なんで純粋関数が出てくんの?そもそも純粋ってなんなの?ピュアなん?」

R子「純粋関数って、副作用とかが無くて、入力によって出力が確定する関数のことやねんけど、これの利点は仕様が分かりやすいことと、ユニットテストがめっちゃ書きやすいことやねー。ピュアやでー」

R子「Reactの純粋関数コンポーネントやったら、引数を JSX のツリー構造に変換するだけのプログラムになるから、デバッグもテストも開発もめっちゃ簡単になるんよー」

 純粋関数。関数型言語の人たちが好むやつ。Reduxなんかも実は制約として、引数を書き換えちゃいけないというルールがあったりする。

 副作用はプログラミング中級者以上を目指すなら、覚えておくべき概念。I/Oや、配列やオブジェクトの中身の書き換え、変数の書き換えなどはすべて副作用。ちなみに本当の純粋関数でI/Oを行うためには、ちょっと特別な概念の導入が必要になるが、関数型言語を布教する記事ではないため省略する。

 ユニットテストでは、副作用は極めて厄介な存在である。対処方法としてはモックを使うなどがあるのだが、一部の人はモックを使ったテストに違和感を覚えるようだ。

R子「ただなー。Flux/Redux + 純粋関数だとどうしても問題があってなー。仰々しすぎて、どうせならコンポーネント自体が状態をもつ方が好ましいときってのがあんねんよー」

J美「Inputコンポーネントで使うような現在入力中の文字列とかって、ストアでもつのはさすがにアホらしいもんね?」

R子「そうそう。Flux/Reduxは素晴らしい革命的なヤツやったけど、なんでもかんでもストアにデータ貯めるのって、それはそれでアンチパターンやねんよー」

 アンチパターン。みんながハマりがちなパターンのこと。Reduxなんかはシングルストア・シングルソースという概念をもつため、ある1つの場所に、細々した情報も全部ため込むため無駄に仰々しい。Reactが難しいって感じたら、だいたいReduxアンチパターンのせいだと思っていい。

J美「それどうやって解決すんの?今までのReactやFlux/Reduxの仕組みじゃどうしようもなさそうだし、せっかくFlux/Reduxで簡単になったものが逆戻りしちゃいそうなんだけど」

R子「そこで一時期流行ったんが、Recompose/HoC っていう考え方やねんなー。JavaScriptって元々関数型言語の流れをくむ言語やから、高階関数を合成することで、純粋関数コンポーネントに、状態をもつ機能を後付けする仕組みやね」

J美「高階関数?」

R子「JavaScriptやったら、高階関数自体は当たり前に使ってるよー。関数やメソッドの引数に関数を指定するというものやから。古い言語ではそれが自由にやりとりできないっていうの多かったけど」

R子「ポイントは関数合成の方やね。高階関数の合成、要するに引数として受け取った関数と別の関数を足し合わせて、ある純粋関数に別の機能をアドインするようなもんやな。これのミソは、純粋関数自体はキレイなままで、状態保持や副作用を合成する別の関数に押し付けたりできるんやでー」

J美「じゃぁ、React + Redux + 非同期ミドルウェア + Recompose っていう組み合わせが流行ってたの?」

R子「そうそう。一部の意識高い系React民に好まれてたんやわー」

J美「でも、それってただでさえ複雑になってたReact + Redux が、さらにややこしくなるってことよね?」

R子「せやねー。結局のところ、純粋関数は便利だけど、機能追加を Recompose/ HoC のやり方でやるのは大変やから、React自体に大きくメスを入れたんよー」

R子「それがReact Hooksっていう、Reactの関数型コンポーネントに、状態とか副作用をもたせる仕組みで、Recomposeがやってたようなことを、React自体がもつようになったんよー」

 React + Redux + 非同期ミドルウェア + Recompose は正確にはコンポーネントは全部純粋関数で書いたうえで、Recompose というライブラリによって、機能を合成していた。

 React Hooksは、React 自体が Recompose のような機能を React 内部をいじることで実現したもの。そりゃ本家本元が React 自体いじる方が確実だ。

J美「React HooksとRecomposeでシェア争いみたいにはならなかったの?」

R子「それがなー。Recomposeの作者もReduxの作者もFacebooks社に入社して React Hooksの開発者側に回ったから、シェア争い以前に趨勢が決まったんよー。Facebook社もうまいこと立ち回ったもんやわー」

 ちなみにRecomposeの開発はもうされないらしい。そりゃまぁそうだろう。

J美「それはそれとしてReact Hooksはどういう機能持ってるん?」

R子「状態をもつ useState、副作用を実行するための useEffectuseLayoutEffect、演算結果を保持するための useMemoとコールバック関数を生成するためのuseCallback、DOMにアクセスするための useRef などやなー。他にもあるけどな」

 コールバック関数は、JavaScriptだと空気のごとく度々登場するやつ。ある関数を実行するときに、処理が終わったとき、他のイベントが生じたとき、エラーが生じたときなどに呼び出される。

J美「全部 use で始まってる?」

R子「せやねー。Hooks関数っていう特定の条件でのみ使える特殊な関数を提供するのがReact Hooksやねん。Hooks関数は必ず use から始まるねん」

J美「特定の条件って?」

R子「クラス型コンポーネントでは使えないから、関数型コンポーネント専用の関数やねん。さらにいうとその関数のトップレベル限定」

J美「トップレベルって??」

R子「if/for文とコールバック関数以外の場所かな。制御構造の内側は禁止されてるってことやね。ここでいうトップレベルってソースコード全体のトップじゃなくて、関数の中でのトップのことやね」

 トップレベルと言っても、ソースコード自体のとトップレベルではなくて、関数でのトップレベルのこと。一段でも何かしらの制御構造などでネストされるとだめ。

J美「だめなパターンいくつか教えてよ」

R子「せやな。こういうのは実際のコードで見たほうがわかりやすいなー」

import React, { useEffect } from 'react'

const Component = props => {
  useEffect(() => {
    console.log('これはOK')
  }, [])

  if (props.hoge) {
    useEffect(() => {
      console.log('これはNG 1')
    }, [])
  }

  for (const line of props.lines) {
    useEffect(() => {
      console.log('これはNG 2'
    }, [])
  }

  props.line.forEach(line => {
    useEffect(() => {
      console.log('これはNG 3')
    }, [])
  })

  const trap = props.hoge ? useState('NG 4') : useState('NG 4')

  return <div>J美大好き!</div>
}

J美「最後のは要らんわー。R子があたしのこと好きなんは知ってる」

R子「NG 1はif文の中やねー。もちろんelseの中でもあかんよー」

R子「NG 2はfor文の中やねー。もちろんwhileの中でもあかんよー」

R子「NG 3はコールバックの中やねー。コールバック関数の中は結構、ナチュラルにhooks関数を書きそうになる罠やでー」

R子「NG 4は分かりづらいけど、三項演算子も結局制御構造と変わらんからやね」

J美「これって結構制約キツくない?」

R子「まー、実際考え方をスイッチせんといかんやろねー」

R子「hooks関数って、Reactコンポーネントに依存した仕組みやねんけど、普通のJavaScriptプログラミングやと、ある純粋な関数が、そのコンテキストに応じて処理内容変えるってできへんやろ?」

J美「Scalaだったら implicit 引数でコンテキスト渡したりするよね」

 Scalaのimplicit引数は、筆者は個人的にはかなり嫌いな存在なのだが、便利な側面もある。わざわざ関数の引数に、変数を指定しなくても暗黙的に引き回されるといった便利機能である。ややこしい操作を意識しなくても勝手に必要な情報が渡っている。implicit引数がない多くの言語ではたとえばcontextみたいな引数を渡すこともある。こういったことは設計のトレードオフとして捉えられることが多い。

R子「せやなー。いうてもScalaやないから、JavaScriptというかECMAScriptの言語仕様でどうにかせんといかんねん。そしたらReact自体のコンポーネント階層の情報を使って、関数にコンテキストを注入するやり方になるわけやなー」

 ECMAScriptは、JavaScriptとして知られるもののうち、言語仕様のみを定めた国際的な仕様。ECMAという欧州の機関が定めるECMA-262が、ECMAScriptである。

J美「なるほど、技術的な理由ってことね」

R子「せやでー。Reactから見ると、自分でコンポーネント関数を管理してるから、それぞれのコンポーネント関数のコンテキスト、つまり自分の親は誰か、どういう状態か?というような情報をもってるわけやねん」

R子「でも、ここに制御構造が入ってもうたら、コンテキストの管理が困難になる、つまり技術的に極めて面倒やから、制御構造はダメっていう制限入れたわけやねー」

 Reactというライブラリから見れば、コンポーネント関数はツリー構造で管理しているため、それぞれのコンポーネント関数はどういうコンテキストを持っているか?というのを知っている。  コンテキストはたとえば、自分の親はどういうコンポーネントか?今保持してるステートは何か?というもの。クラスとインスタンスの関係に似ている。コンポーネント関数はコンテキストと合わさって、ある1つのインスタンスのように扱われる。

第4話 React Hooksというプログラミングスタイル

R子「ただなー。それだけやないんやけどねー。トップレベルに限定されるのは、制御構造を入れて複雑にしたくなかったっていう意図もあんねんよー」

J美「というと?」

R子「React Hooksプログラミングでは、関数型コンポーネントのトップレベルでやることを3つに絞ったんよー。本来の目的であるJSXを返すこと、データを受け取ること、コールバック関数の登録やねー。こうすることで考え方をシンプルにして、人類でも簡単に扱えるようにしたんやねー」

 純粋関数以外の側面を hooks に押し付けた形ともいえる。たとえば副作用なんかはコールバック関数の中で実行してもいいという形にした。  前述のとおり純粋関数には、ユニットテストが書きやすいなどの利点がある。

R子「元々、Facebooks社では、React Hooksによって GUIプログラミングを宣言的なものに立ち返るっていうテーマを掲げとってなー」

J美「宣言的プログラミングっていうと、SQLみたいなやつ?」

R子「まぁせやなー。逐次的実行と宣言的実行の違いやね。React Hooksでは、命令を逐次記述するんやのうて、コールバック関数を登録することで擬似的にやけど、宣言的プログラミングに変えたんよー」

J美「つまり、関数型コンポーネントの本来の役割はJSXを返すものだとして、値を受け取る以外では、宣言的実行、この場合はコールバック関数を登録するだけにしたんだね」

 元々Reactはリアクティブプログラミングとして考えられていたが、リアクティブという性質と宣言的実行の性質がマッチするというのもある。

R子「まぁこれは技術的な理由も1つあってな。この関数は、Reactライフサイクルのあらゆる段階でひたすた呼ばれるんよ。クラス型コンポーネントやったら、ライフサイクルに応じて異なるメソッド呼ばれてたやろ?あれが、関数型コンポーネントやと、その関数に集中してまうんよー」

J美「あー、なるほど。毎回呼ばれるとしたら、重い処理書いたらそれだけでオーバーヘッドがものすごいことになるもんね。でもそれだったら、副作用以外でもマズいケースありそうだよね。重い演算とか」

R子「せやねー。重い演算とか書いてもうたらそこでブロッキングするよー。その対策として、useMemo みたいな関数が重要になってくるわけやなー。場合によっては、重たい演算処理なんかは Worker に投げた方がええやろねー」

 JavaScriptはシングルスレッドであるため、排他処理などが不要なぶん、重い演算があるとブロッキング要因となるため、重い演算は Worker に投げるのが望ましい。Worker は異なるプロセスとして演算などを受け持ってくれる。

J美「useMemoってメモってあるけど、アルゴリズムとかに出てくるメモ化のこと?」

R子「それがなー。微妙に違うねんよー。メモ化ってメモとして記録する容量を多く取るやろ?DP(動的計画法)とかそうやけど、複数の演算を配列に保存するけど、Reactのメモって、メモいいつつ、保持する情報は1つだけやねん。依存変数が前回と同じ場合だけ、その前回の値を返すっていう仕組みやねん。容量が1なキャッシュやね」

J美「つまり、メモ化みたいなアルゴリズム目的じゃなくて、Reactライフサイクルによる呼び出し回数が増大してることの対処のためのキャッシュなんだねー」

 メモ化は、本来であれば配列などに複数の値を保持することで、繰り返し計算を抑制するアルゴリズムだが、Reactのメモはそういう目的のためにあるわけではない。少し名前が紛らわしいと筆者は思うのだが…。

J美「ところで依存変数って何なの?」

R子「useStateuseContextみたいな関数は、無条件でデータを受け取るものやねんけど、useMemoとかuseEffectは第二引数に依存変数っていうのを指定するねんなー。まぁコードみた方が早い思うわ」

type Props = {
  date: Date
}

const CalendarDay: React.FC<Props> = ({ date }) => {
  const [schedules, setSchedules] = useState<Schedule[]>([])

  const displayDay = useMemo(() => {
    if (date.getDate() === 1) {
      return <span>{date.getMonth() + 1}{date.getDate()}</span>
    }
    return <span>date.getDate()</span>
  }, [date])

  useEffect(() => {
    api.get(`/schedule/${date.toString()}`).then(res => {
      setSchedule(JSON.parse(res.body))
    })
  }, [date])

  return <>
    {displayDay}
    {schedules.map(schedule => <div>schedule</div>)}
  </>
}

R子「適当にでっち上げたソースやから適当やけどそこは勘弁やでー。たぶんうごく。このコードは、schedulessetSchedulesっていうステートと、displayDayっていうメモ化された変数と、スケジュールをAPIで取ってくるuseEffectで成り立ってるやろ?で、useMemouseEffectの第2引数が[date]っていう配列になってるのが、依存変数やね」

R子「これらの第2引数は配列を指定するねんけど、[]みたいな空配列やったら、最初に一回実行したらそれっきりって意味。空じゃなかったら、それぞれの要素が一致したら同じモノを返す・Effectなら実行しないって意味やねん」

J美「つまり、CalendarDay コンポーネントのプロパティとして渡したDateが同じ日付だったら再演算しないってこと?」

R子「だいたい合ってる」

J美「だいたいってことは違うケースもあるん?」

R子「実はな、一致の判定の仕方に癖があるねんよ。ECMAScriptのObject.isと同じ判定方式やねん。まぁ簡単にいうと同じようなものでも別のオブジェクトなら別扱いやねん」

const a = new Date(0)
const b = new Date(0)
console.log(a === b) // --> false

R子「同じ日付に見えても、abでは別々のインスタンスやから別もの扱いやねん。これがやっかいなところでなー。チューニングとか考えるなら、この点を忘れちゃいかんねんよー」

J美「ということは、このCalendarDayは、日付が一致するかではなくてオブジェクトが一致するかで判定してる不親切なコンポーネントってわけだよね」

R子「せやなー。呼び出し側で気をつけないといかんパターンやね。もちろん第2引数に渡す値を工夫すれば、日付で一致するように変更は可能やね」

J美「Scalaとかだったら、一致するかはオブジェクト自体が判定メソッドもてるよね」

R子「せやなー。その方が素直な気はするな。けっこうECMAScriptはそういうところで気が回らんところあるなー。これハマりどころやからもう少し詳しく見てくでー。」

const Hoge = props => {
  useMemo(() => {
    ...
  }, [props.items]
  ...
}

const Fuga = props => {
  const items = [1, 2, 3 ,4]
  return <Hoge items={item} />
}

J美「Fuga で固定的に配列作ってるから同じ?」 R子「そう見えるやろ?でもなー、コード的には、配列リテラルによって毎回異なるオブジェクトとして items 生成してるねんよー」

const Hoge = props => {
  useMemo(() => {
    ...
  }, [props.items]
  ...
}

const Fuga = props => {
  const items = useMemo(() => [1, 2, 3 ,4], [])
  return <Hoge items={item} />
}

J美「FugauseMemoは第2引数が空っぽだよねこれ」

R子「せやねー。さっきもちょっとだけいうたけど、useMemoの第2引数が空のときは最初の一回だけしか実行しないっていう意味やから、初回に生成された[1, 2, 3, 4]っていう配列がそのまま次も使われるねん。こういうケースやったら、Hogeprops.itemsは毎回同じものっていう扱いになるんよー」

J美「オブジェクトや関数なんかも全部同じってことだよね?」

R子「せやね。他の言語でいうとポインタが一致してるみたいな考え方やねー」

J美「useMemoって、ポインタを一致させるためにも使えるわけね」

R子「そうそう。useMemoは条件が合えば、同じものを返してくれるね。ちなみにuseCallbackもまったく同じ理由やねー」

J美「あー、なるほど。コールバック関数なんかも変数への代入とかだと都度生成しちゃうから、コールバックを生成する関数が重要になってくるわけね」

J美「ちなみにオブジェクトの一致じゃなくて、中身の一致にしたいときってどうするの?」

R子「手っ取り早いのはJSON.stringifyやなー」

const Hoge = props => {
  useMemo(() => {
    ...
  }, [JSON.stringify(props.items)]
  ...
}

J美「あー、オブジェクトや配列がJSON文字列になるから、文字列比較になるのね。っていうか、文字列や数値だったらオブジェクト一致みたいな処理にはならないってこと?」

R子「そうそう。文字列や数値はそういう意味では扱いやすいよー」

J美「毎回JSON.stringifyを実行するのはさすがに重くない?」

R子「場合によりけりやなー。最適化をしたいけど一致させるのが大変な場合には、JSON.stringifyで無理やり一致させるのも十分ありやと思うで。特に引数を引き回してるようなような場合やなー」

R子「Reactの最適化したいポイントって意外と末端が重要やねんけど、最初からうまいこと最適化を意識して全体を作らないと末端までうまく最適化できないことが多いんよなー。でも、上の階層で下手こいてもピンポイントで最適化するみたいなこともできるねんよ」

J美「JSON.stringifyの重さと、それによって最適化がハマったときのトレードオフが重要ってことね」

 JSON.stringifyはECMAScriptで定義されてる機能で、オブジェクトや配列をJSON文字列に変換してくれるもの。ChromeなどのブラウザだとV8自体がもつ機能であるためかなり高速。ただし、オブジェクトの場合はプロパティの順番などに気をつける必要があるかもしれない。

J美「なんとなく分かってきたけど、React Hooksでコンポーネント作るときって実際どういう感じに作るん?Reduxとどう違うん?」

R子「Redux経験者が、React Hooksで最初に考えるべきことは、コンポーネントをどうやったらほぐせるかやねん」

J美「ほぐす?」

R子「もともとな、React Hooksって、クラス型コンポーネントだと複雑一直線やからそれをシンプルにするっていう触れ込みで発表されたんやけど、いっぺん真面目に React Hooksでコンポーネント書いたら分かるんやけど、愚直に書くとコンポーネント関数がアホみたいに太るねんよー」

J美「あー、クラス型コンポーネントでメソッドとして分割してたものが、1つの関数に書かれたら、その分、巨大関数が出来あがっちゃうわけね」

R子「まぁ実際には、最初は愚直に書いちゃってえーと思うよー。必要に感じたら少しずつほぐしていけばえーねん」

R子「でなー。React Hooksでまともに開発するためには、カスタムフックというのが必須になってくるねんなー。カスタムフックはようするにhooks関数と同じものを自作できるって機能やねー」

J美「hooks関数と同じもの?ステートを持ったり、副作用を実現したりできるっていうのと同じようなもの?」

R子「たとえばFirebaseにアクセスするためのカスタムフックとかやね。useで始まる関数かつ、Hooksのルールを守ってればそれはカスタムフックとして使えるねんよ。たとえば useFirebase とか作るわけやね。しかもそのuseFirebase関数のトップレベルではhooks関数を呼び出せるねんよ」

J美「あー、hooks関数を組み合わせて、より詳細な機能を提供するってこと?」

R子「そやねー。まぁ関数型コンポーネントの中身を外だしするためのものと考えることもできるんよー」

J美「クラス型コンポーネントと比べて、ほんとにHooksって楽になるの?」

R子「どちらかというと、楽にすることができる、みたいな感じやねー。HTMLとJSXの関係みたいなもんやと思ったらえーわー。Hooksで書くだけじゃ楽にはならないけど、楽にするためのテクニックが豊富にある感じ」

R子「たとえば、クラス型コンポーネントでは、ライフサイクルによってメソッドの名前って固定やん?つまり分割粒度がライフサイクルで固定されてまうねんよー。でもhooksやったら、そこは自由に分割粒度を決められるねん。べつ useEffect って何回でも記述してもえーしねー」

J美「あ、本来はライフサイクルだったuseEffectが複数書けるってことは、責務に合わせてuseEffectを分けて書いてもいいってこと?」

R子「そやねー。責務に合わせてuseEffectを分けて書いたら、あとはそれをカスタムフックとして外だしすることもできるから、責務とか意味に対応した分割が自由にできるわけやねー」

J美「なるほどクラス型メソッドだったら、ライフサイクルのせいで、FirebaseへのアクセスとlocalStorageへのアクセスと、テキストエディタコンポーネントの初期化みたいなのが全部同じ場所に書かないといけなかったみたいなのが、それぞれ別物にできるってことね。処理が追いかけやすくなるね」

 責務。プログラミング中級者以上を目指すなら絶対理解しておきたい概念。ある関数、あるメソッド、あるクラス、何かしらそういった塊がなすべき仕事、インターフェース、役割などのこと。責務が増えれば増えるほどしがらみが増えてしまい、コードの読みやすさの低下、コードをいじりづらくなる、動作確認が面倒になる、ユニットテストが困難になるなどの問題をはらむ。

R子「プログラミングって、人間が読んでメンテナンスせんといかんから、どれだけ読みやすいか?どういう意味合いで塊が作られるか?が大切やけど、React Hooksはそれをやりやすいねんよー」

J美「JSXもそうだけど、プログラマがプログラミングとして頑張って設計して開発してメンテナンスできる仕組みを作ることを優先してるってことかな」

J美「ところでhooks関数ってどんなのがあるの?」

R子「よく使うのは、useState, useEffect, useCallback かなー。あとは状況によって useLayoutEffect, useMemo, useRef, useContext を引き出しに入れておけばほとんどケースは対処できるよー」

 詳しくは、実際に、公式のAPIリファレンスを見てほしい。ただ、全部を使うことはそうそうない。 https://ja.reactjs.org/docs/hooks-intro.html

J美「ちなみに、ちょっと気になってたんだけど、副作用を関数型コンポーネントのトップレベルに書いたらどうなるん?」

R子「副作用を検知する仕組み自体はないから、その副作用次第やねー。console.log だけならコンソールがにぎやかになるだけかもしれんねー。ただそれで重くなることもあるかもしれへんよー。あと、下手なことやると無限ループが簡単に生じたりするでー」

J美「無限ループ?どういう場合に生じるの?」

R子「一番わかりやすいのは、ステート更新をトップレベルでやっちゃうことやな。ステート更新って、関数を実行しなおして新しい状態に更新するっていうフローやから、自分で自分を更新し続けるねんよー」