🕐

7GUIsで学ぶReact状態管理Jotai | Counter 編 (1/7)

2022/03/15に公開

はじめに

この記事は、「GUIプログラミングのベンチマークとして提案された7つの課題を題材に、React状態管理ライブラリのJotaiを学んでみよう」というテーマのJotai学習記事の第一回Counter編です。
完成したコードの解説がメインになります。もしご自身で実装してみたい場合はネタバレになってしまうのでご注意ください。

7GUIsとは

言語やライブラリ系のベンチマークといえば計算速度が評価軸にされることが一般的ですが、この7GUIsはいくつかの指標を軸に7つGUIアプリのお題を用意し、それをベンチマークとして提案しています。
2018年頃に話題となったようで、web系だとReact/MobXやSvelteが実装例として掲載されています。
詳しくはこちら。
https://eugenkiss.github.io/7guis/

7GUIs x Jotai の元ネタ

Jotai作者の@dai_shiさんが過去に取り組んでおり、CodeSandboxで既に実装済みなのでそちらを題材として使わせていただきます。
https://blog.axlight.com/posts/learning-react-state-manager-jotai-with-7guis-tasks/

お題:Counter

(以下DeepL翻訳)

課題:言語/ツールキットの基本的な考え方を理解すること。
タスクは、ラベルまたは読み取り専用のテキストフィールドTとボタンBを含むフレームを構築することである。初期状態ではTの値は「0」であり、BをクリックするたびにTの値が1つずつ増加する。
Counterは、言語、パラダイム、ツールキットの基本を、想像できる限り最もシンプルなGUIアプリケーションの1つに対して優しく紹介する役割を果たします。このように、Counterは必要な足場と、GUIアプリケーションを構築するために非常に基本的な機能がどのように連携しているかを明らかにします。優れたソリューションには、ほとんど足場がありません。

回答コード

解説

Atoms

countの値を持つbaseCountAtom、それを派生元としてボタンBを押下してカウントアップする為のcountAtomを定義しています。
countAtomはbaseCountAtomのDerived Atom(派生アトム)と呼びます。

const baseCountAtom = atom(0);

export const countAtom = atom(
  (get) => get(baseCountAtom),
  (_get, set) => {
    set(baseCountAtom, (c) => c + 1);
  }
);

atom(0)とは

atom関数はconfigですが、初期値を与えることが出来ます。今回は0で初期化した値が必要なのでatom(0)と定義できます。初期値0のnumber型なatomです。

Derived Atomとは

atomはatomを元に定義することが出来ます。何かのatomを派生元にしたatomがderived atomです。
Derived atomには、Read-Only atom, Read-Write atom, Write-Only atomの3種類を定義できます。
ここで出てくるcountAtomはRead-Write atomです。atom(readFn, writeFn)の形で定義されています。readFn(read関数)で派生元となるatomの値を参照します。writeFn(write関数)で派生元のatomをどう更新するか定義しています。

https://jotai.org/docs/basics/primitives#atom

より噛み砕いて解説した記事がありますので、あわせてこちらもどうぞ。
https://zenn.dev/tell_y/articles/25ecda0b397a22

countAtomのread関数の定義を見る

countAtomの

(get) => get(baseCountAtom)

がread関数です。
第一引数でget関数(Getter)を受け取ることが出来ます。それを使ってatomの値を参照出来ます。
return(返り値)がcountAtomの値になるので、ここでやっていることは、baseCountAtomの中身(値)を読んで返す、です。countAtomの値はbaseCountAtomの値ということですね。

countAtomのwrite関数の定義を見る

countAtomの

(_get, set) => {
  set(baseCountAtom, (c) => c + 1);
}

がwrite関数です。
第一引数はget関数、第二引数はset関数(Setter)です。set関数を使ってatomの更新をします。
今回はget関数は使わず、set関数のみ使っています。
set関数に対象のatomと更新結果を設定します。(c) => c + 1と関数を設定していますが、このcはupdate関数を実行したときのcountAtomの値です。
update関数といっているのは、const [count, inc] = useAtom(countAtom);incのことです。

ちなみに

実際はbaseCountAtomだけ使って実現可能です。React.useStateと同じですね。

// in atoms.ts
const [count, setCount] = useAtom(baseCountAtom);

// in Counter component
<button onClick={() => setCount((c) => c + 1)}>Count</button>

Write-only atomで表現すると・・・

// in atoms.ts
const countAtom = atom(0);
const countUpAtom = atom(
  null,
  (_get, set) => {
    set(countAtom, (c) => c + 1);
  }
);

// in Counter component
const count = useAtomValue(countAtom);
const inc = useSetAtom(countUpAtom);

こんな感じで書くことも出来ます。(※useAtomValueとuseSetAtomはv1.6.0以降でcoreから提供されています。以前はutilsのみからuseAtomValue,useUpdateAtomとして提供されていました)

Components

Counterコンポーネント一つです。<input />で無くてもいいのですが、お題に忠実に。
onClickにinc関数が指定されていますので、Countボタンを押すとincが実行されます。

const Counter = () => {
  const [count, inc] = useAtom(countAtom);
  return (
    <>
      <input value={count} readOnly /> <button onClick={inc}>Count</button>
    </>
  );
};

useAtom関数

atomを扱うにはコンポーネント内でuseAtom/useAtomValue/useSetAtomなどを使います。

const [count, inc] = useAtom(countAtom);

countがnumberの値です。incが更新関数です。inc関数は実行するとカウントアップしますね。
write関数で定義された内容が実行されるので一応注意してください。(const [count, setCount] = useAtom(countBaseAtom)としたときにsetCountとは同一ではないです)

おわりに

カウンターアプリなので入門中の入門になりましたが、この調子で次回以降も記事にしていこうと思います。

Jotai Friendsとは

いちJotaiファンとして、エンジニアの皆さんにもっとJotaiを知ってもらって使ってもらいたい、そんな思いから立ち上げたのがJotai Friendsです。

https://jotaifriends.dev/

Jotai Friends

Discussion