xstateを使って、多段コンポーネント間でre-renderをなるべく少なく表示する方法

# 問題

```tsx
const [state, send] = useMachine(machine)
```

こうしてしまうと、`send("EVENT")`が起きる度に`state.value`が変わったり、`state.context`が変わったりするので、re-renderが起きてしまう。

極局所的な箇所であればre-renderはさほど気にならないが、コンポーネントが多段で各コンポーネントの描画が重いとかになるとre-renderがとてももっさりして最終的には使い物にならなくなったりする。

# 解決

- useInterpreter
- useSelector
- ReactのContextかJotai

を使う。

## [useInterpreter][1]

```tsx
const service = useInterpret(someMachine);
```

serviceが返ってきて、これは不変。`service.send("EVENT")`しても`service`自体のオブジェクトは変わらないので、re-renderが起きない。

```tsx
const [state, send, service] = useMachine(someMachine);
```

としても`service`は取れるけど、この場合`state`、`send`が変わってしまうのでre-renderが起きてしまう。

## [useSelector][2]

不変のserviceでre-renderが起きなくなったとすると、全く画面更新が起きないので、それはそれで困る。そこでuseSelector。

useSelectorを使うと特定のstateに対して反応して、renderを起こしてくれる。

```tsx
const updated = useSelector(service, (state) => state.matches("updated"))
```

これによって、子コンポーネントとかで特定のstateだけをwatchする形にすれば、不要な再描画が全く起きなくなる。


## ReactのContextかJotai

多段コンポーネント間でuseInterpretで生成したserviceを使わなければならない。

```tsx
// これは毎度呼ぶ度に新しいserviceを作ってしまうので、
// 親子で個別に呼んでしまうとそれぞれのserviceオブジェクトになってしまうので意味がない。
const service = useInterpret(someMachine); 
```

### 回避策1（ダメな例）
 
```tsx
import { useInterpret } from "@xstate/react"
import React from "react"
import machine, { Service } from "./machine"

export const useService = () => {
  const [service] = React.useState<Service>(useInterpret(machine))
  return service
}
```

React.useStateで管理しようとしても、どうもuseInterpret側の実装なのか`useSelector`の動きがおかしい。

### 回避策2

React.Contextを使う。単純に親コンポーネントでuseInterpretからserviceを生成して、contextに保持しておく。子コンポーネントではそれを引き出して使う。

```tsx
// 親コンポーネント 
export const ServiceContext = React.createContext({} as { service: Service })
...

...
const service = useInterpret(machine)

return (
  <ServiceContext.Provider value={{ service }}>
    {props.children}
  </ServiceContext.Provider>
)
...
...

// 子コンポーネント 
...
const {service} = React.useContext(ServiceContext)
// .matches とか .contextとか見てbooleanを返す
const isSomeState = useSelector(service, (state) => state.matches("someState"))
...
```

### 回避策3

[Jotai][3]を使う。基本的にReact.Contextと同じようなこと。initialValuesとして親コンポーネントでserviceを渡しておいて、それを子コンポーネントで使う形。

```tsx
// 親コンポーネント
import { Atom, Provider } from "jotai"
const serviceAtom = atom<Service, Service>(null, (get, set, update) => {
  set(serviceAtom, update)
})
...

const element: [Atom<Service>, Service] = [serviceAtom, useInterpret(machine)]
return (
  <Provider initialValues={[element]}>
    ...
  </Provider>
)

// 子コンポーネント
import { useAtomValue } from "jotai/utils"
...

// 不変だし変更する必要がないので、useAtomValueで値だけを取る
const service = useAtomValue(serviceAtom)
// .matches とか .contextとか見てbooleanを返す
const isSomeState = useSelector(service, (state) => state.matches("someState"))
```

## machineからServiceのtypeを作る方法
`ActorRefFrom`を使って対象の`machineをtypeofで渡す`とService typeが作れるっぽい。

```ts
import { ActorRefFrom } from "xstate"
import machine from "./machine"
type Service = ActorRefFrom<typeof machine>
```

## [全体的にstateの状態で反応したい場合][4]

```tsx
useEffect(() => {
  const subscription = service.subscribe((state) => {
    console.log(state)
    console.log(state.context)
  })

  return subscription.unsubscribe;
}, [service])
```

# 参考

https://dev.to/mpocock1/how-to-manage-global-state-with-xstate-and-react-3if5

[1]: https://xstate.js.org/docs/packages/xstate-react/#useinterpret-machine-options-observer
[2]: https://xstate.js.org/docs/packages/xstate-react/#useselector-actor-selector-compare-getsnapshot
[3]: https://docs.pmnd.rs/jotai/api/core#provider
[4]: https://xstate.js.org/docs/packages/xstate-react/#services