Closed25

npm install uhyo

hajimismhajimism

uhyoさんの記事とか本をローラーしていって、彼の考え方を脳みそにinstallしたい。

もともとNext13のキャッチアップに迫られて、React18にキャッチアップする必要が出てきて、uhyoさんの関連記事読むかーってなって、この際他のもついでにやっちゃおうかなと思いついた次第。なので最近の記事からどんどん巻き戻っていくイメージ。

hajimismhajimism

別の記事でuseフックを見かけたので気になって最初はこれを見ていた。
https://zenn.dev/uhyo/articles/react-use-rfc

以上のことを考えると、useは特殊なフックというよりも、フックの新しい分類を立ち上げる存在であると考えられます。従来のフックたちを「記憶領域を必要とするフック」として、新たに「記憶領域を必要としないフック」という分類ができたというイメージです。
両者に別々の名前を付けてもよいと思うのですが、用語を増やすとユーザーが混乱するでしょうからそれは避けたのではないかと思います。また、use以外に今後「記憶領域を必要としないフック」が出てくるかどうかは不透明なので(それを防ぐためにuseという超汎用的な名前を付けたとも推測できます)、今回useは「特殊なフック」という立ち位置にしたのでしょう。

ここらへんの感覚は、自分ではそうそうたどり着けないなあという感じがして、頭が上がりませんね。

つまり、「Promiseがすぐに解決される」ということを「レンダリング後にマイクロタスクキューが全部消化されるまでに解決される」として定義し、この場合はコンポーネントのレンダリングが成功したと判断し、サスペンドを省略します。

これにより、従来は完全に同期的だった「レンダリング」という処理が、マイクロタスクレベルの遅延であれば待ってくれるという意味で、非同期的な処理になったと考えることができます。この点が今回のRFCの本質的なポイントでしょう。

なるほどが止まりません。わりと革命的な変更なんじゃない?と思いました。

use用の記憶領域はレンダリング時に確保され、そのレンダリングが成功裡に完了すれば記憶領域は破棄されます。

レンダリングごとの記憶領域、というものを考えたことがなかったので目からうろこでした。

他にも重要だなと思った概念

  • Promiseの一級市民化
  • Reactコンポーネントは冪等であるべき

Flutterを書いているときにも考えていたけど、冪等なコンポーネントというか、純粋な関数を単位としてプログラミングをしていくことで静的型と相まってレゴ的な感覚でソフトウェアがつくれる...。これが大事なのかなと思っています。複雑なソフトウェア開発をなるべくシンプルに持っていく。

useとcacheによってライブラリが不要または薄くなるというくだりも面白かった

hajimismhajimism

続編のこちらも
https://zenn.dev/uhyo/articles/react-use-rfc-2

...以上が、useが直にPromiseを受け取るというAPI設計の背景です。Tとloadingを別々に持っているよりは設計が良くなっている感じがしますね。

useによってデータ取得のための中間層がいらなくなるという話の理解が一層深まった。やり方はいろいろあるけど「より良い設計」は存在するということがよくわかる例だった。

useを使う世界ではもはやPromiseはデータであると考えましょう。そうすれば、fetchNoteは「非同期処理を発火する関数」ではなく「Promiseというデータを取得する関数」であると再解釈できます。

個人的には前編も含めていちばん納得度の高い箇所だった。データ取得関数の責務の単純化。

まとめると、「なんでコンポーネントに副作用があんだよ」に対するもう半分の答えは、「副作用はPromiseという抽象の向こうに隠されたので、コンポーネントから見れば副作用ではない」となります。

正直今まで「副作用(effect)」という言葉遣いをよく理解できていなかったんだけど、この記事のおかげで少しわかった気がする。要するに副作用とは「Reactのツリーと実際のDOMツリーをsyncするという”本作用”を果たすために起こってしまう(実際は意図的に起こす)あれこれ」のことで、今回の話ではuseとSuspendによってPromiseがReactの本作用の方で扱ってもらえるようになったので、Promiseの扱いが副作用でなくなった。

Noteが関数として呼び出される。
fetchNote("uhyo")が呼び出されて、Promiseが返る(これをPromise1としましょう)。
useにPromiseが渡されたので、useの内部処理でPromise1がthrowされ、サスペンドが発生する。
ReactはNoteのレンダリングを中断し、Promise1が解決されるのを待つ。
Promise1が解決される。Promise1の結果はReact内部に保存される。
ReactはNoteのレンダリングを再開する。Noteが関数として再度呼び出される。
fetchNote("uhyo")が呼び出されて、Promiseが返る(これをPromise2としましょう)。
useにPromise2が渡されたが、今回はサスペンド明けの再試行なのでPromise2は無視される。代わりに、5で保存されていたPromise1の結果がuseの返り値として使用される。
Noteが無事に返り値を返し、それをReactがレンダリングしてNoteのレンダリングが完了する。

このStep説明のおかげでSuspendの挙動がよくわかりました。その名の通り中断なんですね。
Suspendに限らず、Reactは純粋性というか冪等を前提に色々無視することで何度もレンダリングしてもパフォーマンスを保っているということがなんとなくわかった気がします。

const [arr1, setArr1] = useState([]);       // 2回目以降に作られた [] は無視される
const [arr2, setArr2] = useState(() => []); // [] は1回しか作られない

useStateの引数を関数にできることは知っていましたが、挙動の違いまでは理解していませんでした。このアナロジーでuse(() => promise)の説明をするところは流石だなと思いました。

サスペンド時にわざわざ「コンポーネント単位の記憶領域」を破棄する理由は筆者のReact力が足りないので説明できません。理由が分かる方はぜひコメントでご教授ください。

考えたけれど「そういう思想」くらいの答えしか浮かばなかった...。Suspendするということは基本的には「初回レンダリングだった」と考えられるので、after suspentionのレンダリングでもあたかも「初回レンダリングだった」かのように見せたい、的な。

hajimismhajimism

Suspendのことをもっと知りたくなったのでこちら
https://zenn.dev/uhyo/books/react-concurrent-handson

ErrorBoundary的なデザインパターンでfallback領域を限定できて嬉しい。以前から似たようなデザインパターンでLoadingContainer的なコンポーネントを実装していたのでわりとスッと概要をつかめた気がする。Promiseの情報をReactが読み取ってくれることでloadingかどうかのフラグが要らなくなってより使いやすく。

「グローバル変数の原始的な実装→Mapとハッシュキーの一歩進んだ実装」の流れ、個人的には学びが多かった。新しいReact公式ドキュメントに「原始的なuseReducerを実装する」っていうセクションがあるんだけど、それをやったときの感覚と似ている。「プリミティブなものを作ってみた」系の威力は大きい。

07でLoadableをハンズオンしたことでuseの理解も少し深まった。

hajimismhajimism

上の本の中で言及されていた
https://qiita.com/uhyo/items/255760315ca61544fe33

言い方を変えれば、Loadableは非同期処理の扱いをReactに丸投げ(throwだけに)することによってインターフェースから非同期処理を隠蔽しているのです。

handsonしたとき、Loadableで非同期処理が隠蔽できることに一瞬びっくりしたんだけど、この記事を読めばそれがthrowのおかげであることがよくわかる。

大域脱出という単語は初めて聞いた。
https://ja.wikibooks.org/wiki/JavaScript/例外処理

例外は大域脱出に使うこともできます。大域脱出とは、入れ子になった制御構造の内側から外側に制御を戻すことです。ラベルを伴わないbreakやreturnは最内側の制御構造(for/while/switchと関数)を抜け出すだけですが、例外をthrowすると文や関数を超えて制御が移ります。 この性質を利用すると二重以上のループや関数を脱出することができるのです。 しかし、大域脱出目的の例外の使用には慎重になってください。breakやreturnをラベルと共に使用することで、ほとんどの場合は例外を使うことなく大域脱出を達成できます。

英語ではNon-local exitsなどと言うらしい。

記事のコメントが荒れていた。そこで紹介されていた古典的な?記事を流し読み。
https://web.archive.org/web/20190723080235/http://local.joelonsoftware.com/wiki/間違ったコードは間違って見えるようにする

ここまでで、プログラマの成長の3つの段階を示した。

  1. きれいなものと汚いものの区別がつかない。
  2. きれいさについて表面的な考えを持っている。そのほとんどはコーディング規則に合っているかどうかというレベルだ。
  3. コードのきたなさについて、表面的でない小さな兆候も嗅ぎつけるようになり、気になってコードを直すようになる。

これよりもさらに高いレベルがあり、それこそ私が本当に話したいと思っているものだ。
4. コードを直すために汚いコードに対する嗅覚を生かせるような仕組みを、最初から作り込むようになる。

パン工場の例と、この分析は面白いと思った。Reactでうまい例を出そう思ったけど思いつかない。2は多分「letが怖い」みたいなことだと思う。

hajimismhajimism

「Promiseをthrowするのはなぜ天才的デザインなのか」で引用されていた資料に目を通す。
https://qiita.com/uhyo/items/4a6315bfccf387407631

冒頭で述べた通り、このシリーズでは筆者がConcurrent Modeを試してみた経験を基にして、Concurrent Mode時代に適応したReactアプリケーションの設計を提案します。もちろんこれが唯一解であると主張したいわけではありませんが、最も基本的な考え方として通用するものだと考えています。

こういうのが言えると、「きちんと理解した」ってことなんだろうなあ。

より正確に言えばConcurrent Modeは非同期処理の扱いをより疎結合に表現する手段を提供してくれるというところでしょう。従来我々が手ずから扱っていた非同期処理対応の一部分を、Reactが組み込みの機能として受け持ってくれるという見方もできます。

いくつか関連記事を読んだのでそろそろ当たり前に理解できるようになってきた。uhyoさんありがとうございます。

これまでの復習みたいになってていい感じ。実際はこちらのほうが古いんだけども。

hajimismhajimism

ついでなのでシリーズ読破しますか。
https://qiita.com/uhyo/items/ba49b25f0a206e933e4d

そして、このワーニングに対する対処法はずばりuseTransitionを使うことです。useTransitionを使うことで、ステートの更新でサスペンドが発生した場合に元々のステートを基にフィードバックを描画できるのです。

自分はここでuseTransitionさんと初対面です。

こう使うらしい。

 const [startTransition, isPending] = useTransition({
    timeoutMs: 10000
  });

...

        <button
          onClick={() => {
            // ステート更新をstartTransitionで囲む
            startTransition(() => {
              setUsersFetcher(new Fetcher(fetchUsers));
            });
          }}
          // isPendingがtrueのときはdisabledに
          disabled={isPending}
        >
          {isPending ? "Loading..." : "Load Users"}
       </button>

startTransitionの内部で行われたステートの更新がサスペンドを発生させた場合、変更後ではなく変更前のステートがレンダリングされます。ただし、このとき変更前のステートではuseTransitionが返すisPendingがtrueになっています。

とのこと。

isPending用意しちゃったらfallbackの意味なくない?って思ったけど、ユーザーへのフィードバックは細かく対応できるようにってことなのかしら。記事中にも「fallbackはいわば最終防衛ライン」とある。

また、timeoutMsで設定した時間を超えない限り、Suspenseのfallbackで指定した内容は表示されなくなります。useTransitionをきちんと使っている限りは、Suspenseのfallbackはいわば最終防衛ラインのような扱いになり、高頻度でユーザーが目にするものではなくなります。

以下の例はuseTransitionのありがたみがよくわかる。

例えば、「画面Aから別の画面Bに遷移したい。ただし、画面Bを表示するには非同期処理によるデータの読み込みが必要」という場合を考えてみましょう。しかも、データの読み込み中は画面Aに留まって読み込み中の表示にしたいとします。このとき、非同期処理が完了し次第画面Bに遷移するようにするには、とにかく画面Bをレンダリングしてサスペンドさせる必要があります。しかし画面Bをレンダリングしてしまうと画面Aは消えてしまいます。

この問題に対して、useTransitionは「古い状態(画面A)と新しい状態(画面B)を同時に扱う」という方法で対処します。これはちょうど、gitでブランチを切って2つのバージョンのステートをメンテナンスするようなものです(Reactの公式ドキュメントでもこの例えが用いられています)。これによって、「まだ画面には反映されないけど新しいステートをレンダリングする」ということが可能になりました。

hajimismhajimism

https://qiita.com/uhyo/items/4c45672874874a2aad11

useRefに話を戻しますが、Concurrent Modeではrefオブジェクトへのアクセス(特に書き込み)は副作用であると考えるべきです。先ほど説明したように、レンダリング中にrefオブジェクトに書き込むと、サスペンドしたレンダリングの影響がそれ以降に残ってしまうため、コンポーネントのレンダリングが純粋でなくなるからです。refオブジェクトは、useEffectのコールバック内やイベントハンドラなど、副作用が許された世界でのみアクセスすべきです。refオブジェクトはもはや完全に副作用の世界の住人なのです。

他の記事ではここまで深堀りされていなかったトピック。



個人的にはSuspenseを子コンポーネント(まさにフェッチが発動するところ)に封じ込めるのか、レイアウト的なイメージで親に書いておくのかの検討も気になるな。Suspendさせることは親の責務だとはあまり思わないけど、どの例を見ても親に書いてある。

hajimismhajimism

https://qiita.com/uhyo/items/52ab8e2cab30b0811294

末端のコンポーネントがuseTransitionを呼ぶのか、それとも上層のコンポーネントにまとめるのかというのは難しいテーマであり、筆者もまだ結論を出せてはいません。昨今のステート管理ライブラリはアプリのステートをコンポーネント間で分散させるのではなく一箇所のストアにまとめるという戦略を取っていることが多いですが、Concurrent Mode時代のステート管理ライブラリはそれに加えてuseTransitionも一緒に管理してくれるのかもしれません。ただ、複数のトランジションを定義するには複数回useTransitionを呼び出すしかないので、上手に管理するのは難しそうですね。

例えば、複数の種類のステート更新に際して、同じトランジションを利用することができます。その典型例はやはりページ遷移の場合であり、どのページからどのページに遷移する場合でも同じトランジションを使うのが普通でしょう。同じトランジションというのは結果としては同じフィードバックエフェクトということになりますが、これを単なる「コードの共通化」ではなく「(実体として)同じトランジション」という一段上の抽象度で表現できることは特筆に値します。APIデザインの妙と言えるでしょう。

transitionはstate更新に伴って発生するので予めセットにしておけばいいと思いきや、疎結合にしておくことで複数のstate更新を同一のtransitionでまとめることができ、transitionをstate更新より一段上の概念として捉えられるという話が一番おもしろかったですね。

ページ遷移の設計の話はすごく難しいなと思いました。自分もuhyoさんが推してる方を選ぶかなという気がするけれど、チームメンバー全員がこれにすんなり合意してくれるかっていったら微妙というかかなり学習と議論が必要だなと。単純にRootに寄せていくほうが考えることが少ないというか慣れている形なのでね。

hajimismhajimism

https://qiita.com/uhyo/items/fa443351fd4444dacaf7

記事中にConcurrent modeに特化したデータフェッチングライブラリの話題が度々出てきたので調べてみたらこれが出てきた。
https://github.com/dai-shi/react-hooks-fetch

Contextパターンを使っているけど基本的な実装はハンズオンで出てきたMapで管理するものと同じだった。
ブログ
https://blog.axlight.com/posts/developing-a-react-library-for-suspense-for-data-fetching-in-concurrent-mode/

hajimismhajimism

このシリーズ、2020年に書かれているからなー、恐ろしいなー

hajimismhajimism

https://zenn.dev/uhyo/books/react-concurrent-handson-2

isPendingによる制御、書いてみると結構いいなーと思った。

06あたりの裏の世界の話はぶっちゃけよくわからなかった。また別の機会に。

トランジションによる世界の分岐はよくgitのブランチに例えられますが、表の世界から裏の世界を作るステートの差分がコミットに相当し、表の世界ブランチが更新されたときは裏の世界ブランチは新しい表の世界ブランチにrebaseされると理解できます。

rebaseがよくわかってないのでこの例もよくわからなかった。今すぐ調べてもいいけどここはなんとなく「baseが変えられる」くらいに理解しておいて、git編のスクラップを別で作ってまとめて学ぶ。Hash treeとかと合わせて理解したいので。

hajimismhajimism

https://zenn.dev/uhyo/articles/readable-code

読みやすさの基準については特に異論なし。

readonlyは普段使ってないな。今まで入った職場でも意識して使っている人はいなかったように思う。でもたしかに使うほうが情報量は増えるな。

ちなみに、やろうと思えばgetFontSizeの返り値の型を10 | 16 | 24というユニオン型にすることもできるのですが、ここではnumberと書かれています。これにも意味があり、「10、16、24といった具体的な数値には意味がない」ということを読み手に伝える役割を果たしています。この関数は何らかの数値を返す関数なのであり、個々の数値に依存すべきではないということを関数のインターフェースを通じて示しているのです。

ここも、自分の考えが及んでいないところだなと思った。特定できるなら特定しちゃえってなりそう。返り値をユニオンで限定しておけばジャンプせずともhoverしたときに何が返ってくるかわかるので。

hajimismhajimism

https://zenn.dev/uhyo/articles/usememo-time-cost
https://qiita.com/uhyo/items/5258e04aba380531455a

一応述べておきますが、筆者の主張は「useMemoもdivも削れ」ではありません。「微々たるものだからどちらも気にしなくていい。しかしuseMemoを気にするのにdivを気にしないのはおかしい」というのが筆者の考えです。

ベンチマークの計測ってやり方わからないので、写経しに行ってもいいかもしれない
https://github.com/uhyo/react-usememo-bench


https://qiita.com/uhyo/items/5258e04aba380531455a

このコンポーネントにuser1を渡していた場合、せっかくのReact.memoの効果が発揮されません。userに渡されるオブジェクトが毎回別物になるのでReact.memoは別のデータが来たと判断してShowUserを再レンダリングしてしまうからです。user2を渡した場合は、中身が変わらない限り同じオブジェクトを渡しているのでShowUserの再レンダリングが行われません。

このように、useMemoは、React.memoを使っているコンポーネントに渡されるオブジェクトが変わるのを最小限にするという目的で使うのがもっとも主流のユースケースです。ちなみに、useCallbackに関しても「オブジェクト」を「関数」と読み替えれば全く同じことが成り立ちます。

なるほどです。

もちろんランタイムに債務を押し付けすぎるのは良くありませんが、そもそもReactなどを使っている時点である意味ランタイムにかなりの部分を押し付けていますから今さらです。

やべ、ここよく意味がわからなかった。文章の意味がと言うよりは、ランタイムに責務を押し付けるという感覚と、Reactを使うことの意味がわかってない。

また、今回styleオブジェクトはdivに渡されることが明らかです。実は、divのようなネイティブのHTML要素に対しては毎回別のオブジェクトを渡してもパフォーマンス上の問題がありません。そのため、ここではuseMemoを使わないことを選択しています。

知らなかった。より具体例を見るためにuhyoさんのコードをもっと読みに行ったほうがいいかも。

このことから、useMemoを書くかどうかは今いる関数の中だけ見て決める(関数の外をわざわざ見に行かない)という考え方につながります。関数というのはひとつの開発単位であり、関数を実装するときにその関数全体に視野を広げるのは普通のことですが、関数の外まで視野を広げるべきではありません。それでは関数を分けた意味がかなり薄れるからです。

mayahさんも「考えなければならないことを減らせる様に書く」っておっしゃってて、カテゴリとしては同じ話に聞こえた。

https://blog.uhy.ooo/entry/2021-02-23/usecallback-custom-hooks/

簡単な言葉で言い直せば、結局のところ「返り値の関数はuseCallbackで囲んだほうがカスタムフックの汎用性が高くなるからそうしろ」ということです。 場合によってはそのuseCallbackが無駄になるかもしれませんが、観測できるかどうかも分からないオーバーヘッドよりは設計上の要請のほうを優先したいというのが筆者の考えです。

この記事の話もちゃんと同じに読めた。よかった。

そもそもReactの世界では、「値が違う」(===ではない)ことが色々な処理のトリガーになります。 React.memoもそうですし、useStateやコンテキストなども“違う”値が入ることが再レンダリングを引き起こします。 ですから、違わないものは違わないと明確にする(===になるようにする)ことには、単なるパフォーマンス最適化だけではなくロジック上の意味が付与されます。 毎回違うものを返すということは、本当に毎回意味が異なるものを返していると受け取られます。 少なくとも、返されたものを使う側はそのように扱わなければいけません。 そうしないと、useMemoの依存リストを間違えて厄介なバグを生み出すことにも繋がりかねないからです。

そうだよね。React(というかJS/TS)において、何が===で何がそうでないかをきちんと区別できなければならない。

hajimismhajimism

https://blog.uhy.ooo/entry/2021-09-07/empty-div/

これもスタイリングのためだけにdivを使っているという点で、この記事のテーマに少し似ています。筆者は、これに関しては明確に、全く問題ないからどんどんdivを重ねていいと思っています。

自分もどちらかといえばHTMLの文書としての意味(セマンティクス)を崩さない限りはスタイリングをやりやすくするためのdivなどはどんどん書いているな...。例で上がってたflexbox制御のためのダミーもよくやる。

自分はこれとは別に、スタイリング層は使い捨てすべき、使い捨てられるようにすべきと考えているので、「どうでもいい」という感覚に近いかもな。

hajimismhajimism

https://qiita.com/uhyo/items/3bc5f951f922804ede51

ここで定義したArrayOfLength型は、型レベル再帰を行なっておりロジックという感じが出ていますね。慣れていない方には読みにくく感じるかもしれませんが、repeat関数の返り値がT[]に比べてより具体的になっており、ドキュメンテーションの精度という点では明らかに改善されています。

自分も正直ArrayOfLength型を現時点ではきちんと解読できてないのだけれど、「情報量が増えたほうが読みやすい」というuhyoさんのポリシーに則っていることは十分に理解できる。

uhyoさんが例に上げていたrepeat関数の文脈に乗るならば、自分はTSの型を「具体的な処理を”集合”という一段抽象的な解釈に置き換えて読みやすくするもの」だと思っている。repeat関数の中身を直接読むのではなく、「名前がrepeat」っていうヒントに加えて「Tとnumberを受け取ってT[]が出てくる」っていうアウトラインがあるおかげで、「Tをnumberの回数分repeatした配列を返す関数かな」っていう意味が読み取れる。

そういう意味では、別に配列の要素数まで型レベルでわからなくても、「T[]っていう集合」くらいでもいい気がする。

でもまあ結局の所、コメントにあったこれが自分にも当てはまっていると思うので、どちらかというと複雑な方を読めるようにまたは書けるようになりたい。

安全性を重視されて、TS を使いこなしていて、尊敬します。
結局のところ、僕にはそこまで頑張れない、というのが「シンプルにしよう」とか「最後は押し込めて隠蔽しとこう」とかいう発想に繋がってると思っています。

hajimismhajimism

https://qiita.com/uhyo/items/aae57ba0734e36ee846a

まとめると、asを正しく利用するためには以下のことに注意する必要があります。

  • TypeScriptの敗北を明らかにする。この場合はTypeScriptが手続き的な操作でオブジェクトを作るコードを正しく型推論してくれないのが問題でした(やるのはかなり難しいでしょうから仕方ありませんが)。
  • 危険性の影響範囲をできるだけ小さい関数に閉じ込める。上の例で見たように、正しいインターフェースを持つ関数を定義し、asの危険性をその内部でしか露呈しないようにしましょう。これにより、危険性が影響する範囲を明確にして人間による安全性のチェックの負担を減らすことができます。また、危険なasを使う目的が明確になります。

この観点と、isの活用例のところが非常に参考になりました。

hajimismhajimism

https://qiita.com/uhyo/items/d74af1d8c109af43849e

型ファーストというのは、型があることを前提にプログラムを設計し、型の恩恵を最大限受けるための書き方をすることで達成されます。型ファーストでプログラムを書くと、型のことを考えずに書いたJavaScriptプログラムとは異なる形になることがあるのです。

ここはしっかりと理解しておかなければいけない点だろう。

クラスコンポーネントでの記述は読み飛ばしてしまったけれど、ユニオン型を用いてStateやPropsの実態を詳細に記述する話は納得。よく見る話だけどもしかしてこの記事が発祥?

hajimismhajimism

https://qiita.com/uhyo/items/cea1bd157453a85feebf

useReducerを使う場合はステート更新関数(dispatch)は自動的にステートに非依存になります(reducerはそもそも「現在のステートを受け取って次のステートを計算する」というものであるため)。

なぜuseReducerが必要なのか、この記事を読んだ皆さんはしっかりと説明できることでしょう。ステート更新関数がステートに非依存であることは、React.memoの活用には必須だからです。

実際のアプリ開発においては、アプリが複雑化するにつれて、あるステートと別のステートが関わりを持ち始めるかもしれません。もっと具体的に言えば、あるステートを更新するときに別のステートを見る必要が発生するかもしれません。そのときがuseReducer導入のサインです。

自分はuseReducer+useContextパターンが割と好きでよく使っている。ContextパターンでもuseMemoを使えばレンダリングの最適化ができることを確認した。

const NumberInput: FC<{ index: number }> = ({ index }) => {
  const { values } = useContext(CountStateContext);
  const dispatch = useContext(CountDispatchContext);

  return useMemo(
    () => (
      <p>
        {console.log("rerender input :", index)}
        <input
          type="number"
          value={values[index]}
          onChange={(e) =>
            dispatch({
              type: "input",
              index,
              value: e.currentTarget.value,
            })
          }
        />
        <button
          onClick={() =>
            dispatch({
              type: "check",
              index,
            })
          }
        >
          check
        </button>
      </p>
    ),
    [values[index]]
  );
};

function App() {
  const [state, dispatch] = useReducer(reducer, {
    values: ["0", "0", "0", "0"],
    message: "",
  });
  const { values, message } = state;

  return (
    <CountStateContext.Provider value={state}>
      <CountDispatchContext.Provider value={dispatch}>
        <div className="App">
          {values.map((_, i) => (
            <NumberInput key={i} index={i} />
          ))}
          <p>合計は{sum(values)}</p>
          <p>{message}</p>
        </div>
      </CountDispatchContext.Provider>
    </CountStateContext.Provider>
  );
}

以下を参考にしました。ありがとうございます。
https://qiita.com/soarflat/items/b154adc768bb2d71af21

hajimismhajimism

https://qiita.com/uhyo/items/6a3b14950c1ef6974024

useRefによる状態管理はuseTransitionに無視されるから好ましくないよという話。ただuseSyncExternalStoreを使えればいけるかも。

Q. トランジションとかサスペンドとか言われても全然分からないし、サンプルコードも読めないんだけど。

A. 確かに多少前提知識が必要でしたね。しかしご安心ください。筆者がこれまでに書いた以下の記事およびZenn本を順番に読めばすべて理解できます。今すべて読む時間がないとしても、何がアンチパターンなのかということだけは理解して帰ってください。

これはこのスクラップがまさにそうなので保証できます笑

hajimismhajimism

ここからはメインディッシュ?であるTypeScriptの型システム探訪が始まるのだけれど、一連を把握するにはこれまでの倍以上の時間がかかりそうなのでまとまった時間が取れるときに再開します!
https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a

このスクラップは2022/12/12にクローズされました