React+Recoil+TypescriptでTodoリスト実装

### 前準備
#### Recoilのインストール
他のライブラリなどと同じくパッケージのインストールを行います。

```bash:yarn
yarn add recoil
yarn add @types/recoil
```

```bash:npm
npm i recoil
npm i @types/recoil
```

### 実装
#### RecoilRoot
まずはプロジェクトの大本である`App`を
`RecoilRoot`タグで囲みます。この`RecoilRoot`タグに囲まれていればRecoilによって管理されている値を参照できるようになります。


```tsx:App.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { RecoilRoot } from 'recoil';

ReactDOM.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
```

#### Atom
AtomはAtomを識別するための`key`とデフォルトの値である`default`をatomメソッドに渡すことによって作成されます。

```ts:atom.ts
import { atom } from 'recoil';

const todoAtomState = atom<string>({
    key: "todoAtom",
    default: "",
  });

export default todoAtomState;
```

##### Atomの変更
入力用フォームのコンポーネントを作成します。
状態を更新するためには`useSetRecoilState`を使用します。

```tsx:InputForm.tsx
const InputForm = () => {
  const setTodo = useSetRecoilState<string[]>(todoAtomState);
  return (
    <>
      <input onChange={(val) => setTodo(val.target.value)}></input>
    </>
  )
}

export default InputForm;
```

##### Atomの取得
次にTodoリストの表示用コンポーネントの実装です。
Atomの状態の取得には`useRecoilValue`を使用します。

```tsx:TodoList.tsx
const TodoList = () => {
  const todo = useRecoilValue<string[]>(todoAtomState);
  return (
    <>
      <span style={{ display: "block" }}>{todo}</span>
    </>
  )
}

export default TodoList;
```

```tsx:App.tsx
const App = () => {
  return (
    <div className="App">
      <InputForm />
      <TodoList />
    </div>
  );
}

export default App;
```

##### おまけ
Hooks APIの`useState`のような記述でもgetとsetを行うことができます。

```ts
[state, setState] = useRecoilState(**atom**)
```

このような感じで`InputForm`での更新が`TodoList`に反映されているのが確認できました。

![atom-string.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256522/b89b7db1-3a8c-4f3f-b5f1-3b18263f8c70.gif)


しかしこのままではただの値の更新をしただけになってしまうため、***リスト***を作成していきたいと思います。

### 本編
#### 入力フォーム
↑まではinputの`onChange`イベントでAtomの値を更新していましたが、ここをHooks APIの`useState`を用いて一時的に入力された情報を保持するように変更します。

```tsx:InputForm
const InputForm = () => {
  const [state, setState] = useState<string>("");
  return (
    <>
      <input onChange={(val) => setState(val.target.value)}></input>
    </>
  )
}
```

追加ボタンの`onClick`イベントでAtomsで管理している配列に値の追加を行います。
`useSetRecoilState`は更新前の値を取ることができるので、これを使用して配列の更新を行います。

```tsx:InputForm
const InputForm = () => {
  const [val, setVal] = useState<string>("");
  const setTodo = useSetRecoilState<string[]>(todoAtomState);
  return (
    <>
      <input onChange={(val) => setVal(val.target.value)}></input>
      <button onClick={() => setTodo((oldState) => [...oldState, val])}>
        Add
      </button>
    </>
  );
};
```

#### リスト表示
Todoの配列の長さをもとに簡単なリストを表示するように変更します。

```tsx:TodoList
const TodoList = () => {
  const todo = useRecoilValue<string[]>(todoAtomState);
  return (
    <ul>
      {todo.map((val) => {
        return <li>{val}</li>;
      })}
    </ul>
  );
};
```

↓がこの時点での動作です。
想定通りリスト表示が機能するようになりました。
![atom-list.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256522/3057d34e-2a54-86df-92bd-3fbc535739f7.gif)

#### 達成状況の管理
Todoリストなのでその項目が達成できているのかどうかの管理も必要となります。

項目名の`name`と達成状況の`finished`を持つ`ITodo`を定義します。

```tsx
export interface ITodo {
  name: string;
  finished: boolean;
}
```

完了状況を更新するための`statusChanged`を実装、チェックボックスの`onChange`イベントに登録します。
atomで管理している完了状況をもとに、完了した項目は→のようにに~~打ち消し線~~で消すようにします。


```tsx:TodoList
const TodoList = () => {
  const todo: ITodo[] = useRecoilValue<ITodo[]>(todoAtomState);
  const setTodo = useSetRecoilState<ITodo[]>(todoAtomState);

  const statusChanged = (targetVal: ITodo) => {
    const index = todo.findIndex((item: ITodo) => item === targetVal);
    const newTodo: ITodo = { ...todo[index], finished: !targetVal.finished };

    setTodo([...todo.slice(0, index), newTodo, ...todo.slice(index + 1)]);
  };

  return (
    <ul style={{ listStyle: "none" }}>
      {todo.map((val) => {
        return (
          <li>
            <input type="checkbox" onChange={() => statusChanged(val)}></input>
            <span
              style={
                val.finished
                  ? { textDecoration: "line-through" }
                  : { textDecoration: "none" }
              }
            >
              {val.name}
            </span>
          </li>
        );
      })}
    </ul>
  );
};
```

最終的にこのような完了状態の管理ができるTodoリストが出来上がりました。

![recoil-through.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256522/ffaeba17-580d-6bd9-7f18-e9d0fbdf2968.gif)

## おわり
状態管理の単位が個々で独立しているためシンプルに管理を行うことができそうです。また、状態管理が独立していることで、無駄なコンポーネントの再描画がなるためパフォーマンス面において優れています。
個人的にはReduxと比較しActionやReducerが存在せず、状態を操作できる点は魅力的で、それらがないことで準備段階のハードルも低くReduxよりも使いやすく高評価でした。

りぽじとり
https://github.com/mugi111/react-recoil-todo