React Recoil事始め

## はじめに
React/React Nativeの新しいState managementとして`Recoil`と言うのがReleaseされています｡ Recoilを使って超簡単なアプリを作って､Recoilの使い方をざっくりチラ裏しておきます｡

## Recoil概要
* 新しいState management Library (`Redux`だったり､`GraphQLのApollo`の置き換え)
* StateをGlobalに一括管理し､Stateの値を使いたいComponentで利用を宣言すると､Stateの更新がされた時に､そのComponentにも自動でRe-Render処理が走る
  * イメージとしては､GlobalにStateを管理し､ReactのContextで必要なComponentにState値を渡すイメージ
  * これにより､不要なRe-Renderが抑制され､各種Performanceの恩恵を受ける事が可能
* Stateの`参照`, `更新`, `Event発火時だけ参照`がMethod的に分けられている
  * Stateが更新されても､`Stateを更新するだけのComponent`, `Button Click時にStateを参照するComponent`等にはRe-Renderが走らない
  * これにより､不要なRe-Renderが抑制される
* 使い方がReact Hookと親和性あり

## Recoilサンプル1 (最低限のRecoil機能)
* ボタンをクリックしたら､atomとselectorの値が更新される
* atomとselectorを参照しているcomponentがRe-Renderされる
* [このSiteで](https://codesandbox.io/s/epic-hofstadter-p48xj)以下のCodeを実際に動作させる事が可能

```js

import { AppRegistry } from "react-native";
import React, { Component, useMemo, useCallback } from "react";
import { Button, Text, View } from "react-native";
import { RecoilRoot, useRecoilValue, useSetRecoilState, atom, selector } from "recoil";

const clickCountState = atom({
  key: "clickCount",
  default: 0
});

const clickCountSelector_floorBy5 = selector({
  key: "clickCountSelector_floorBy5",
  get: ({ get }) => {
    const count = get(clickCountState);
    return Math.floor(count / 5);
  }
});

function TextOne() {
  const clickCount = useRecoilValue(clickCountState);
  const clickText = useMemo(() => `Click count from atom - ${clickCount}`, [clickCount]);
  console.log("TextOne is updated");
  return <Text>{clickText}</Text>;
}

function TextTwo() {
  const clickCount = useRecoilValue(clickCountSelector_floorBy5);
  const clickText = useMemo(() => `Click count from selector - ${clickCount}`,[clickCount]);
  console.log("TextTwo is updated");
  return <Text>{clickText}</Text>;
}

function ComponentWithRecoil() {
  const setClickCountState = useSetRecoilState(clickCountState);

  const onPress = useCallback(() => {
    setClickCountState((prevValue) => prevValue + 1);
  }, [setClickCountState]);
  console.log("ComponentWithRecoil is updated");

  return (
    <>
      <TextOne />
      <TextTwo />
      <View style={{ width: "30%", margin: 10 }}>
        <Button title="Click!" onPress={onPress} />
      </View>
    </>
  );
}

class App extends Component {
  render() {
    return (
      <RecoilRoot>
        <ComponentWithRecoil />
      </RecoilRoot>
    );
  }
}

AppRegistry.registerComponent("App", () => App);
AppRegistry.runApplication("App", {
  rootTag: document.getElementById("root")
});
```

### 簡単な解説
#### 1. Stateはatom/selectorで管理される
* atomはStateの最小単位､selectorはatomに何らかの処理を加えた物
  * selectorは､Network Call等の非同期処理や､複数のatom/別のselectorの値を合わせた計算結果を管理する
  * selectorは､使っている(Subscribeしている)atomが更新されると､自動で更新がかかる
  * stateを使う側から見ると､`atom`なのか`selector`なのかは意識する必要は無い
* atomを更新すると､関連するselectorが更新され､更新されたatom/selectorを使っているcomponentのRe-Renderが走る
* atomだけでなくselectorの値もSet(更新)が可能 - [ここにSample作ってみました](https://codesandbox.io/s/nostalgic-water-lm26i)
    * selectorのSetter関数から渡されたset関数で別のatom/selectorの更新が可能 (その結果､通常のatom更新の処理が行われる)
    * (selectorの更新はイマイチUse Caseがピンと来ない...普通はatom更新で済むのだと思います)

#### 2. Component側からは`useRecoilValue()`でStateの値を参照可能
* ここで参照したatom/selectorが更新されると､ComponentにRe-Renderが走る

#### 3.  Component側からは`useSetRecoilState()`でStateのSetter関数を取得可能
* Component側から､atom/selectorの値を更新出来る
* useSetRecoilStateはあくまでSetter関数だけなので､(Stateの値自体を参照していない限り)ComponentにはRe-Renderが走らない

#### 4. `useRecoilState`は､Stateの値の参照とSetter関数取得を同時に行う
```js
const [value, setValue] = useRecoilState(someAtom)

// は､↓の2行と等しい
 
const value = useRecoilValue(someAtom);
const setValue = useSetRecoilState(someAtom);
```

### 補足
* selectorの値は(現状)memoizeされていない｡つまりatomが更新されたがselectorの値が変わらないケースにおいても､selectorを参照しているcomponentにはRe-Renderが走る｡これは[将来的には直そうとしている](https://github.com/facebookexperimental/Recoil/issues/314)模様
* `atomFamily`, `selectorFamily`というのもあるが､atom/selectorに引数を渡す機能が追加された物
  * Arrayのatomに対して､indexを渡すとそのindexの部分を返すような使い方


## Recoilサンプル2 (もうちょっとRecoilの機能を使う)
* ボタン1をクリックしたら､atomと非同期処理のselectorが更新される
* 非同期処理のselectorを参照しているcomponentは､loadingの最中はloading indicatorを表示する
* ボタン2をクリックしたら､callbackが発火し､そのTimingでatomを参照してcomponentをRe-Renderする
* [このSiteで](https://codesandbox.io/s/tender-resonance-y8f51)以下のCodeを実際に動作させる事が可能


```js

import { AppRegistry } from "react-native";
import React, { Component, useMemo, useCallback, useState } from "react";
import { Button, Text, View } from "react-native";
import { RecoilRoot, useRecoilValue, useSetRecoilState, useRecoilCallback, useRecoilValueLoadable, atom, selector } from "recoil";

const clickCountState = atom({
  key: "clickCount",
  default: 0
});

export const clickCountAsync = selector({
  key: "clickCountAsync",
  get: async ({ get }) => {
    const count = get(clickCountState); // This "get" has to come before async/await line, otherwise will not re-computed when atom value is updated.
    await new Promise((resolve) => setTimeout(resolve, 1000));
    return count;
  }
});

function TextOne() {
  const clickCount = useRecoilValue(clickCountState);
  const clickText = useMemo(() => `Current click count - ${clickCount}`, [clickCount]);
  console.log("TextOne is updated");
  return <Text>{clickText}</Text>;
}

function ComponentWithRecoil() {
  const setClickCountState = useSetRecoilState(clickCountState);
  const onPress = useCallback(() => {setClickCountState((prevValue) => prevValue + 1)}, [setClickCountState]);
  console.log("ComponentWithRecoil is updated");
  return (
    <>
      <TextOne />
      <View style={{ width: "30%", margin: 10 }}>
        <Button title="Click!" onPress={onPress} />
      </View>
    </>
  );
}

function ComponentWithRecoilCallback() {
  const [clickCount, setClickCount] = useState(0);
  const onPress = useRecoilCallback(({ snapshot }) => async () => {
    const clickCount_value = await snapshot.getPromise(clickCountState);
    setClickCount(clickCount_value);
  });
  const clickText = useMemo(() => `Click count update only when button clicked - ${clickCount}`, [clickCount]);
  console.log("ComponentWithRecoilCallback is updated");
  return (
    <>
      <Text>{clickText}</Text>
      <View style={{ width: "30%", margin: 10 }}>
        <Button title="Click to get current count" onPress={onPress} />
      </View>
    </>
  );
}

function ComponentAsyncWithLoadingState() {
  const clickCountLoadable = useRecoilValueLoadable(clickCountAsync);

  if (clickCountLoadable.state === "hasValue") {
    const clickText = `Click count from async selector - ${clickCountLoadable.contents}`;
    return <Text>{clickText}</Text>;
  } else if (clickCountLoadable.state === "loading") {
    return <Text>...loading</Text>;
  } else if (clickCountLoadable.state === "hasError") {
    return <Text>...error!</Text>;
  } else {
    return <Text>...something else!</Text>;
  }
}

class App extends Component {
  render() {
    return (
      <RecoilRoot>
        <ComponentWithRecoil />
        <ComponentAsyncWithLoadingState />
        <Text> ─────────────────</Text>
        <ComponentWithRecoilCallback />
      </RecoilRoot>
    );
  }
}

AppRegistry.registerComponent("App", () => App);
AppRegistry.runApplication("App", {
  rootTag: document.getElementById("root")
});
```

### 簡単な解説
#### 1. selectorにはasync処理を入れる事が可能
* selectorの中で複数の非同期Selectorの値を処理したい場合には､[waitForAll](https://recoiljs.org/docs/api-reference/utils/waitForAll)などを使うと良い

#### 2. `useRecoilCallback`はCallbackなどの中でatom/selectorの値を参照時に使う
* User操作などのTimingでComponentの値を更新したい時に使う
* atom/selectorの値が更新されても自動的にはRe-Renderされない

#### 3. `useRecoilValueLoadable`は非同期のselectorの値の参照時にLoading Indicatorの表示が可能
* React Suspenseを使えば同様の実装が出来るが､それのSyntax Sugar