LoginSignup
5
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-30

前準備

Recoilのインストール

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

yarn
yarn add recoil
yarn add @types/recoil
npm
npm i recoil
npm i @types/recoil

実装

RecoilRoot

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

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メソッドに渡すことによって作成されます。

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

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

export default todoAtomState;
Atomの変更

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

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を使用します。

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

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

export default App;
おまけ

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

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

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

atom-string.gif

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

本編

入力フォーム

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

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

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

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の配列の長さをもとに簡単なリストを表示するように変更します。

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

↓がこの時点での動作です。
想定通りリスト表示が機能するようになりました。
atom-list.gif

達成状況の管理

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

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

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

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

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

おわり

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

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

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2