👻

Jōtaiでクライアントのステートをイイジョータイにしタイ

2024/02/14に公開

今見えているもの、これはJōtaiによるステート管理です。というより、私達のグローバルステートをより簡単に管理できるその例が表現されているものです。この記事の終わりまでにわかるようになります。

素晴らしい useState

Reactはとても素晴らしく、Reactそのものでとてもシンプルなコンポーネントのステートを管理するものが提供されています。それは useState というもので、ユーザーのブラウザ上で一時的にシンプルなデータをシンプルなコンポーネントで管理するのにとても向いているものです。皆様も簡単に理解できるよう、具体的な実装を観察してみましょう。

この例では、それぞれの <VeryUsefulComponent />count が管理されており、  setCount の引数に数を渡すことにより、 count が更新されます。この記法ではそれぞれ<VeryUsefulComponent /> 同士では値が独立して管理されていますね。

Component内で変化する値の管理はこのような方法で簡単に実装できるのが useState のとても良いところであり、そして難しいところでもあります。さてどのような状況が難しそうでしょうか?ちょっと考えてみましょう。

複数のComponentでステートを管理するむずかしさ

例えば、こんな状況はどうでしょうか?あるコンポーネントは子のコンポーネントで変化する値を取りたいですし、そのコンポーネントでも値を変化させたい、みたいな。皆さんならどう実装しますか?うーん難しいですね。ちょっと手元で実装をしながら考えてみてください。

useState を使ってやるならばこのような実装ができるかもしれません。

ちゃんと動いてそうですね。一見素晴らしそうなこの実装も実は弱点があります。例えばこの渡したいStateが増えたら大変ですよね。もしくはより入れ子構造になったりしたらこのStateを子ComponentのPropsとして引き回さなければならないのでとても大変です(Prop Drilling とか言ったりしますね)。

useContext で値を引き回す

Reactのコミッターもこれはわかっていて、スマートな対応を示しています。 useContext というReact hookを使った方法を見てみましょう。

<VeryUsefulContext.Provider /> 配下のComponentで VeryUsefulContext を呼び出すことで、 <VeryUsefulContext.Provider /> で渡した値を子コンポーネントで引き回すことができ、子コンポーネントではPropsにその値を入れる必要がなくなっているので、拡張が容易になったことがわかります。子コンポーネントでも、親コンポーネントでも、この値を操作できるのでとても楽そうです。

でもこれはあるツリーの一部の配下のみで値を引き回すことができることや、ContextProviderを挿入することで、ツリーが一つ深くなることに対する抵抗感、このContextがどのような値を持っているかはProviderにいれるときに決定される不安感(ある程度は軽減できるとは思いますが)が私にはありました。

もっとシンプルに、まるで useState を使うときのような使い心地はないのでしょうか?

Jōtai で気軽にコンポーネント間で値を引き回す

このような課題感を解決するのにJōtaiはとても良い選択肢です。Jōtaiを説明する前に先程のコンポーネントをJōtaiで書き換えてお見せしたほうがいいでしょう。

Componentの構造はほぼ useState と同じなのがわかると思います。 veryUsefulCountAtom がこのウィンドウ上のグローバルなステートとして各コンポーネントで共有されており、それを親も、子も、並列している要素でもこれを操作することができましたね。

Jōtaiはグローバルなステート管理としてはとても軽量で、シンプルな利用の体験を提供してくれます。atom でステートを宣言・初期値を設定し、 useAtom でそのステートの読み取り・更新を処理します。シンプルに使うならこれらの値はすべての useState の場所で共通の値が利用され、コンポーネントの親子を超えた関係にもその値の共有・操作を提供します。もちろん、この共有は適切に境界をもたせることで、よりコンポーネントの可用性を向上させることが出来ます。ここからはいくつかのユースケースを検討してみましょう。

複数の状態をもたせる

グローバルステートを持つのに便利なJōtaiですが、もちろんそのステートを区切ることも出来ます。 <Provider> でその領域を囲うことでそれを実現できます。値を取らないContextのような動きで、とてもシンプルですね。

状態の中に別の状態をもたせる

<Provider> で囲うことで状態を区切ることが出来ますが、別の状態をこの中に持たせたいと思うこともあるかもしれません。その場合も考慮されており、 Store を利用することで実現します。

useAtom にStoreを渡すことで、特定のStoreの状態を共有することが出来ました。これらはProviderの内外問わず指定されたStoreの中で管理されるようになるので、コンテキストに依存しないステートを持ちたい時はとても使える手法なのかもしれません。

まとめ

もう一度最初の例を見てみましょう、あなたはこの一見複雑そうな状態管理が、実はとてもエレガントで、そしてシンプルに記述されていることに気づき、Jōtaiでイイジョータイを保ちたくなったかもしれませんね。

(※この記事は 3Blue1BrownJapan を見ながら書かれました。)

Discussion