pmndrs/jotaiのドキュメントをざっと読んでみた

# [core][1]

## atom生成
- atom
- atomFamily
- atomFrozenInDev
- atomWithImmer
- atomWithReducer
- atomWithReset
- withImmer

## atom使用
- useAtom
- useAtomCallback 再描画抑止
- useAtomValue
- useBridge
- useImmerAtom
- useReducerAtom
- useResetAtom
- useSelector 廃止 (selectAtomに変更)
- useUpdateAtom 再描画抑止

## 特殊系
- focusAtom
- freezeAtom
- selectAtom


## atom

それ自体には状態を持たず、値はProviderに保存される。
参照するコンポーネントが全てunMountされるとProviderの値も消える。
どこで作っても良くて（コンポーネント内外問わず）、[Reactのstateに入れても良い][5]。

### debugLabel

React Dev Toolsを使い、Providerを見ると`DebugState`という項目があり、ここにatomの値が確認できる。
debugLabelを使わないとatomの名前は連番になるのでどれがどれか分かりにくい。この為debugLabelを使ってわかりやすく出来る。

### onMount

```js
const anAtom = atom(1)
anAtom.onMount = (setAtom) => {
  console.log('returnするまではonMountで実行される')
  setAtom(c => c + 1) // 初期値の設定などに便利
  return () => { ... } // unMount時に実行される
}
```

これを指定すると、そのatomが最初にProviderに保存される時に実行される。
全ての参照するコンポーネントが無くなるとunMountが実行される（`return () => ...`は必要がなければ返さなくていい void可）。

## Provider

atomの値が保存される場所。基本コンポーネントRootに一つあれば良い。

### initialValues

初期値の指定が可能。

### scope

状態管理上、複数Providerを持ちたい場合はscopeを指定してやれば、それぞれのProvider対atomで管理できる。

## useAtom

React.useStateに似てる使い方。

```js
const anAtom = atom(1, (get, set, update) => {...})

...

const [value, setValue] = useAtom(anAtom)
```

ここで言う`setValue`は1つの値だけを取り、それが`(get, set, update) => {...}`の`update`部分に渡されることになる。


## useBridge/Bridge

よく分からない。


# [utils][2]

## useUpdateAtom

```tsx
import { atom, Provider, useAtom } from "jotai"
import React from "react"

const countAtom = atom(0)

interface Props {}

const CounterDisplay: React.FC<Props> = (props) => {
  console.log("CounterDisplay:")
  const [count] = useAtom(countAtom)
  return <>{count}</>
}
const Counter: React.FC<Props> = (props) => {
  console.log("Counter:")
  const [, set] = useAtom(countAtom)

  return (
    <button
      onClick={() => {
        set((prev) => prev + 1)
      }}
    >
      up
    </button>
  )
}

const index: React.FC<Props> = (props) => {
  console.log("index:")
  return (
    <Provider>
      <CounterDisplay />
      <Counter />
    </Provider>
  )
}
export default index
```

こんな感じで同じatomを別のコンポーネントで参照していて、一方は値のみ参照、もう一方は値の更新のみ担当みたいな感じで使っている場合

1. ボタンを押す
2. イベントが発生
3. setを実行
4. countが更新される
5. CounterDisplayコンポーネントが再描画
6. Counterコンポーネントも再描画（atomが更新されているので...）

6.が無駄だよねってことで

```js
import { useUpdateAtom } from "jotai/utils"

...

const Counter: React.FC<Props> = (props) => {
  const set = useUpdateAtom(countAtom)

...
```

のようにアップデート関数だけを抽出しておけば、atomの値の変化にコンポーネントが反応しない＝不必要な再描画がない。  

多岐にわたるコンポーネントで同じatomを使用する場合、再描画を抑えるために出来るだけatomはatom系で作っておいて、useUpdateAtomを使えるところは使ったほうが良さそう。

### atomWithReducer例

> 先にatomがあって、useReducerAtomを使うとuseUpdateAtomは併用できない

```js
const reducer = (prev, action) => {
  if (action.type === "inc") return prev + 1
  if (action.type === "dec") return prev - 1
  throw new Error("unknown action type")
}
const counter = atomWithReducer(0, reducer)

const A: React.FC<Props> = (props) => {
  console.log("A:")
  const [count, dispatch] = useAtom(counter)
...
}

const B: React.FC<Props> = (props) => {
  console.log("B:")
  // ここで普通にこうしてしまうと、Aでdispatchされた際にこのBも再描画される
  // const [, dispatch] = useAtom(counter)
  const dispatch = useUpdateAtom(counter)
  return (
...
}
```



## useAtomValue

useUpdateAtomはアップデート関数だけを抽出するのに対して、useAtomValueは値だけを抽出する（Recoilに合わせたAPIらしいが、再描画を抑えるためではないと思う、シンタックスシュガー的な？）。
`const [value, ] = useAtom(atom)`でも同じ気がする... （よく見てみたら[実際にそう][3]）

## [atomWithReset][4]

atom生成時とかに初期値設定してても、一旦変更した後、「あー初期値に戻したい」ってなったら結構面倒くさくない？
という事で初期値の戻せるatomがあれば良いよねという感じのatom。

useResetAtomと併用する必要がある。


## useResetAtom

atomWithResetのリセット機能付きatomを、実際にリセット（初期値に戻す）時に実行するfunctionを生成するフック。

```js
import { atomWithReset, useResetAtom } from 'jotai/utils'

const count = atomWithReset(100)
...

const reset = useResetAtom(count)

...
<button onClick={() => { reset() }}>初期値（100）に戻す</button>
```

### RESET 

atomWithResetで作ったatomならRESETという特殊なSymbolが使えて、これを元に独自のリセットを実装できる。


## useReducerAtom

atomを使ってreducerを簡単に作れるフック。reducerで返す値がatomのsetにそのまま渡されるので、返す値＝新たな値。

## atomWithReducer

やってることはuseReducerAtomと同じだけど、

useReducerAtom

- atomを作る
- useReducerAtomにこのatomとreducerを指定する

atomWithReducer

- atomを作ると同時にreducerも指定する

atomを作る時に最初からreducer使うことが分かっているのなら、atomWithReducerを使うかどうかの違いと思う。

## atomFamily

[Map][6]のようにatomを束ねて一纏めにしたい時に便利。（TODOリスト的な不特定多数なTODOを管理したい時とか）


引数は

1. [必須] 保存されるatomとなるものの初期値をfunctionで指定 [参照][7]（`atom(defaultValue)`部分と同じ）
2. atomをどういう風にアップデートするか（`atom(..., (get, set, update) => {...})`部分と同じ） [参照][7]
3. 読み出す際にどいう言う比較して、一致する値とするか [参照][8]

```js
                //  1.の部分 |  2.の部分
const anAtom = atom(1,      (get, set, update) => {...})
```

最初に入れた値が[Mapのキーになる][9]。

```js
import { atomFamily } from 'jotai/utils'

const todoFamily = atomFamily((name) => name)

todoFamily('foo') // => foo
todoFamily({id: "abc", value: 123}) // => {id: "abc", value: 123}
```

ドキュメントに書かれていはいないが`.remove`でfamily内の特定のatomを削除できる。

```js
const aFamily = atomFamily((x) => x)

aFamily('foo')
aFamily.remove('foo')

const param = {id: "abc", value: 123}
aFamily(param)
aFamily.remove(param) // 比較する関数を指定していないので、削除する時は全く同じオブジェクトが必要（内部でMapを使っている）

const bFamily = atomFamily((obj) => obj, null, (a, b) => a.id === b.id)
bFamily({id: "abc", value: 123})
bFamily.remove({id: "abc"}) // 比較する関数を指定しているので、idだけ渡せばこの場合はOK
```

また、`.setShouldRemove`というのもある。これは通常使う必要はなく、[どうしても独自のガーベッジコレクション的なものを実装したい][10]場合に設定する。
（基本的にfamily追加時のミリ秒を持っているので、それで比較するなど...）


## ~~useSelector~~ 廃止（selectAtomが追加）

```js
useSelector(anAtom, selector, equalityFn)
```

複数のメンバーを持つオブジェクトをatomにしてる場合、一部が更新されたら全体を参照するのではなく、一部の更新は一部のみで対応して再描画を抑えたい時に便利。

内部で`useMemo`のdependenciesに使っているので、`selector, equalityFn`はコンポーネントの外に書ければそうしたほうが良い。そうじゃなければ`useCallback`を使って逐次オブジェクトとして変わらないようにしなければ再描画が常に行われることになってしまう。  
dependenciesが多くてuseCallbackとか気を使うのであれば、後から出てくるfocusAtomのほうが楽かも。

## selectAtom

useSelectorと基本同じだけど、フックではなくatomに変更。

## useAtomCallback

[フックが使えないコンポーネント以外でatom使いたくない？][11]
って言うことから追加されたけど、結局useフックなのでコンポーネントの中からしか使えない...のはなぜ。
多岐にわたるコンポーネントで同じatomを参照している場合、atomの値が変更されるとどうしても使用している全てのコンポーネントの再描画が起きてしまう。  

このフックを使えば、値の変更を随時表示させるとか計算させる **必要がない（再描画不要）** んだけど、値の参照はクリックイベントの時だけしたい、とか出来る。（useUpdateAtomの値版というか...）

これも`useMemo`を使っているので、渡すcallback関数はコンポーネント再描画毎に逐次変わらないようにuseCallback使わないと無駄な再描画が起きる。

> callbackをラップしたPromiseを返してくるので、async/awaitを使わないといけない

```tsx
import { atom, Provider, useAtom } from "jotai"
import { useAtomCallback } from "jotai/utils"
import React from "react"

const a = atom(0)

const A: React.FC<Props> = (props) => {
  console.log("A:")
  const [count, set] = useAtom(a)
  return (
    <>
      {count}
      <button
        onClick={() => {
          set((prev) => prev + 1)
        }}
      >
        up
      </button>
    </>
  )
}
const B: React.FC<Props> = (props) => {
  console.log("B:")
  const callback = useAtomCallback(React.useCallback((get) => get(a), []))
  return (
    <>
      <button
        onClick={async () => {
          console.log("callback():", await callback())
        }}
      >
        call back
      </button>
    </>
  )
}

interface Props {}
const index: React.FC<Props> = (props) => {
  console.log("index:")
  return (
    <>
      <Provider>
        <A />
        <B />
      </Provider>
    </>
  )
}
export default index
```

因みにドキュメントには書かれていないが、setも出来るし、callbackにパラメータを渡すことも出来る。

```tsx
const callback = useAtomCallback(
  React.useCallback((get, set, arg) => {
    console.log("set:", set) // atomをsetしようと思えば出来る
    console.log("arg:", arg) // callback(1)すれば、1が来る
    return get(a)
  }, [])
)

...

<button
  onClick={async () => {
    console.log("callback():", await callback(1))
  }}
>
  call back
</button>


```



## freezeAtom

atomをimmutableにしてしまい、デバックしやすくするため。
atomにオブジェクトとかを入れていたとして、どこかで期待以外の値に変更されるなどが起きた場合、そのイベント事前に固まらせておいて、エラーを起こさせてデバックできるとか？


## atomFrozenInDev

freezeAtomと同じだけど、NODE_ENV=developmentの時だけ固まる。


# [immer][15]

使うには別途immerが必要。

```
yarn add immer
```

## atomWithImmer

immerを使ったことがあるならわかるけど、複雑なオブジェクトをimmutableとして扱わずにガンガン変更して、そのままでいいので便利。


```tsx
import { atomWithImmer } from "jotai/immer"

const counterAtom = atomWithImmer({ count: 0 })

const Counter: React.FC<Props> = (props) => {
  const [counter, setCounter] = useAtom(counterAtom)
  return (
    <button
      onClick={() => {
        // 通常だと新しいオブジェクトが必要
        // setCounter((counter) => {
        //   const temp = {...counter}
        //   temp.count += 1
        //   return temp
        // })
        setCounter((counter) => {
          counter.count += 1
          // オブジェクトなら値を変更して何も返さないでいい、返してもいいけど
          // プリミティブな値ならその値を返す
        })
      }}
    >
      {counter.count}
    </button>
  )
}
```

## withImmer

atomWithImmerと基本同じだが、すでにあるatomをImmer化したatomを返す。

## useImmerAtom

通常のatomをimmer化したものとして使う。  
※atomWithImmerやwithImmerでimmer化したatomと併用すべきではない。

```js
const counterAtom = atomWithImmer(0)
const regularAtom = atom(0)
const immeredRegularAtom = withImmer(regularAtom)

...

const [value, setValue] = useImmerAtom(counterAtom) // 良くない
const [value, setValue] = useImmerAtom(regularAtom) // これは良い
const [value, setValue] = useImmerAtom(immeredRegularAtom) // 良くない
```


# [optics][12]

[atomを一緒くたに使うのは簡単だけど、バラすのはどう？][13]、と言うのから出来たoptics。

使うには別途[optics-ts][14]が必要。

```
yarn add optics-ts
```

複雑な階層を持つオブジェクトをatomとした場合、全体を変更するのではなく、パーツパーツで切り出してそれぞれを個別のatomにしてしまえば管理しやすいよね？的な。（`useSelector`は参照のみだが、こちらは参照と変更ができる。）  
切り出したとて大元のatomへも変更の通知は行くので便利。

## focusAtom

例は単純に切り出しただけだけど、[optics-ts][14]は色々出来る（Lens、Prism、Isomorphism [用語が独特]）んで要参照。

```jsx
import { atom } from 'jotai'
import { focusAtom } from 'jotai/optics'

const objectAtom = atom({ a: 5, b: 10 })
const aAtom = focusAtom(objectAtom, (optic) => optic.prop('a')) // 切り出す
const bAtom = focusAtom(objectAtom, (optic) => optic.prop('b')) // 切り出す
const Controls = () => {
  // a, bそれぞれ変更しても、objectAtomも逐次変更される
  const [a, setA] = useAtom(aAtom)
  const [b, setB] = useAtom(bAtom)
  return (
    <div>
      <span>Value of a: {a}</span>
      <span>Value of b: {b}</span>
      <button onClick={() => setA((oldA) => oldA + 1)}>Increment a</button>
      <button onClick={() => setB((oldB) => oldB + 1)}>Increment b</button>
    </div>
  )
}
```



[1]: https://github.com/pmndrs/jotai/blob/master/docs/api/core.md
[2]: https://github.com/pmndrs/jotai/blob/master/docs/api/utils.md
[3]: https://github.com/pmndrs/jotai/pull/213/files#diff-21d05ead297c1a75412c630b87330992a0909dca9686cf801efc462c72d31cb3R4
[4]: https://github.com/pmndrs/jotai/blob/master/docs/guides/resettable.md
[5]: https://github.com/pmndrs/jotai/blob/master/docs/introduction/basics.md#atoms-can-be-created-on-demand
[6]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map
[7]: https://github.com/pmndrs/jotai/blob/v0.13.2/src/utils/atomFamily.ts#L86
[8]: https://github.com/pmndrs/jotai/blob/v0.13.2/src/utils/atomFamily.ts#L66
[9]: https://github.com/pmndrs/jotai/blob/v0.13.2/src/utils/atomFamily.ts#L90
[10]: https://github.com/pmndrs/jotai/pull/158
[11]: https://github.com/pmndrs/jotai/issues/60
[12]: https://github.com/pmndrs/jotai/blob/master/docs/api/optics.md
[13]: https://github.com/merisbahti/jotai-optics/tree/b526fd76e9deeaf36a2063fdab521e49bda9a6fd
[14]: https://github.com/akheron/optics-ts
[15]: https://github.com/pmndrs/jotai/blob/master/docs/api/immer.md