LoginSignup
2

More than 3 years have passed since last update.

状態管理 recoil使ってみた。

Last updated at Posted at 2020-12-17

こんにちは株式会社Atraeでフロントエンドエンジニアをしている加藤です。
この記事は、Atrae Advent Calendar 202017日目の記事です。
業務でrecoilを使う機会があったので、recoilについてお話ししようと思います。

Recoil

Facebookが開発した状態管理ライブラリ。
react hooksで使われる事が前提の設計となっている。
非同期処理にも対応しているみたいです。
reduxは、最初から丁寧な設計をしないと後から変更する事が難しい(既に1つの木構造になってしまっている)ですがrecoilはstate(atoms)を分ける事ができるので管理しやすいです。

詳しくは下記Recoilのドキュメントをご覧ください。
https://recoiljs.org

Recoilの導入

recoilをインストールする

yarn add recoil
__app.tsx
import { NextPage } from 'next';
const App: NextPage<AppProps> = ({ Component, pageProps }) => {
  return (
    <RecoilRoot>
      <Component {...pageProps} />
    </RecoilRoot>
  );
};
export default App;

atom

デフォルト値とキーを設定します。
キーはユニークになるようにします。

atom.tsx
export const TextAtom = atom({
  key: "TextAtom"
  default: text: '',
});

作成したatom(TextAtom)をuseRecoilStateに渡します。
useStateと同じ使い方で使用できます。
useRecoilStateの他にuseRecoilValueとuseSetRecoilStateがあります。
useRecoilValueは読み取り専用です。
useSetRecoilStateは書き込み専用です。

a.tsx
  const A: FunctionComponent = () => {
  const [text, setText] = useRecoilState(TextAtom);
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value);
  };
  return (
    <div>
      <input value={text} onChange={(e) => handleInputChange(e)} />
    </div>
  );
};

export default A;

違う画面で同じ関数を使いたい場合

A画面,B画面で同じ関数を使いたい場合(更新したいstateが別)があるかと思います。
共通で使いたい関数をまとめます。(カスタムHooksを作成します)
あとはhandleInputChange()をA画面B画面で呼び出して使うだけです。

atom.tsx
export const TextAtom = atom({
  key: "TextAtom",
  default: {
    textAtomA: '',
    textAtomB: '',
  },
});
useTextState.tsx
export const useTextState = () => {
  const [text, setText] = useRecoilState();
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, key: string) => {
    setText({ ...text, [key]: e.target.value });
  };
  return {
    handleInputChange,
  };
};

複雑な処理を共通化したい場合は複雑な処理の値の変換部分を関数化します。
ドキュメントをご覧ください。
https://recoiljs.org/docs/basic-tutorial/atoms/

item.tsx
function TodoItem({item}) {
  const [todoList, setTodoList] = useRecoilState(todoListState);
  const index = todoList.findIndex((listItem) => listItem === item);

  const editItemText = ({target: {value}}) => {
    const newList = replaceItemAtIndex(todoList, index, {
      ...item,
      text: value,
    });

    setTodoList(newList);
  };

  const toggleItemCompletion = () => {
    const newList = replaceItemAtIndex(todoList, index, {
      ...item,
      isComplete: !item.isComplete,
    });

    setTodoList(newList);
  };

  const deleteItem = () => {
    const newList = removeItemAtIndex(todoList, index);

    setTodoList(newList);
  };

  return (
    <div>
      <input type="text" value={item.text} onChange={editItemText} />
      <input
        type="checkbox"
        checked={item.isComplete}
        onChange={toggleItemCompletion}
      />
      <button onClick={deleteItem}>X</button>
    </div>
  );
}

function replaceItemAtIndex(arr, index, newValue) {
  return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
}

function removeItemAtIndex(arr, index) {
  return [...arr.slice(0, index), ...arr.slice(index + 1)];
}

おわり

使ってみて思ったことは、recoil自体使われはじめてから時間が経っておらず
ベストプラクティスな設計はまだよくわかってないです。
ただreduxよりもシンプルに実装ができ学習コストも高くないのは良かったです!

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
2