Reactの新しい状態管理ライブラリ
「Recoil」とは? Reduxとの違いを解説

50
17
96

Reactの開発において、状態管理の方法は注意深く検討する必要があります。現状Reduxが大きい勢力ではありますが、以前の記事『ベストな手法は? Reactのステート管理方法まとめ』でも紹介した通りさまざまな状態管理の手法が現在でも編み出されています。今回は状態管理ライブラリ「Recoil」についての概要と簡単な使い方、Reduxとの思想の違いについて解説します。

Reduxによる状態管理の懸念点

Reduxに限らず、多くのReact状態管理ライブラリはアプリケーション全体で一貫していることを保証してくれます。これにより、状態管理を一か所にまとめられるというメリットがあります。しかし、Reduxは状態管理を行なっているストアがひとつであるがゆえに、アプリケーション上のデータを常に上書きします。たとえばストアの状態を空オブジェクトのみで更新してしまうと、アプリケーション上のデータはすべて消えてしまうのです。

こういった懸念点を踏まえると、Reduxを用いずReactのみで状態管理を行うのもひとつの手です。

Context APIの制約

Reactのみで状態管理を完結させようとすると、Context APIを用いてアプリケーションの状態を管理させることになります。基本的に問題はないのですが、Context APIにはいくつかの制約があります。

たとえばContext APIで管理されているReactコンポーネントの状態を変更するためには祖先コンポーネントまで辿ってツリーを更新しなくてはいけません。大元のコンポーネントが巨大だった場合はそのぶん更新するツリーも大きくなるため、再レンダリングのコントロール難易度が上がってしまいます。うまく管理しないと巨大なツリー更新によるパフォーマンスの低下を招いたり、コンポーネントの状態が意図せずリセットされてしまうことが起こりかねません。

Recoilとは?

Recoilは、Context APIが抱えるこれらの制約・問題を解決するためにFacebookによって提唱された実験的な状態管理ライブラリです。「Atom」「Selector」と呼ばれる単位を使用してアプリケーションデータを管理し、各Atomには一意のキーとそれが管理するデータの一部が含まれています。各Selectorは複数のAtomにもとづく派生状態の一部で、Atom・他のSelectorを受け取る純粋な関数として定義します。これらの単位をHooks APIで操作しながら状態管理を行うのがRecoilです。

Recoilの基本的なアーキテクチャは、2014年にFacebookによって提唱されたFluxにもとづいた作りとなっており、Reactが現在開発中であるConcurrent ModeやReactの新機能との互換性も考えられて設計されています。

Recoilの使用方法

Recoilを利用するには、npmもしくはyarnでrecoilをプロジェクトに導入します。

npm install --save recoil

RecoilRootの作成

Recoilによる状態管理をアプリケーションで使用するには、RecoilRootというコンポーネントで囲ってあげる必要があります。

<RecoilRoot>
  <Component />
</RecoilRoot>

このコンポーネントは、ReactのContext APIのようにルート直下のコンポーネントの状態管理を担当します。

Atomsの作成

Atomsは、ReduxでいうところのStoreに相当します。明確な違いとしては、Reduxはアプリケーション単位での状態管理であるのに対し、Atomsは一つひとつが状態を保持しているという点です。

// TODOリストの状態を保持する Atoms を作成。
const todoListState = atom({
  key: "todoListState",
  default: [],
});

export const TodoList = () => {
  // useRecoilValue() のHooks APIを使って
  // コンポーネントに適用
  const todoList = useRecoilState(todoListState);
  return (
    <>
      {todoList.map((todoItem) => (
        <TodoItem key={todoItem.id} item={todoItem} />
      ))}
    </>
  );
};

Atomsは一意のキーを持っており、Hooks APIのuseRecoilState()で状態を共有できます。

Selectorsの作成

Selectorsは複数のAtom・他のSelectorを受け取る純粋な関数として定義し、他のデータに依存する動的データを構築できます。useRecoilValue()のHooks APIで呼び出します。

// TODOリストの状態を保持する Atom を作成。
const todoListState = atom({
  key: "todoListState",
  default: [],
});

// TODOリストのフィルターを設定する Atom
const todoListFilterState = atom({
  key: "todoListFilterState",
  default: "Show All",
});

// Selector 関数の定義
const filteredTodoListState = selector({
  key: "filteredTodoListState",
  get: ({ get }) => {
    const filter = get(todoListFilterState);
    const list = get(todoListState);
    // ReduxのReducerのように、
    // アクションに応じて状態を変更して返す
    switch (filter) {
      case "Show Completed":
        return list.filter((item) => item.isComplete);
      case "Show Uncompleted":
        return list.filter((item) => !item.isComplete);
      default:
        return list;
    }
  },
});

Recoilで非同期アクションを取り扱う

Reduxなどで非同期の状態変更アクションを呼び出す際には、Redux-thunkやRedux-sagaなどといった別のミドルウェアを通して管理する必要があります。Recoilには非同期通信用のAPIが提供されているため、React バージョン16.6から提供されたPromiseをキャッチできるSuspenceコンポーネントを使って実現できます。これにより、スマートな実装が可能です。

Suspenceコンポーネントについては、『React今昔物語』で詳しく解説していますのでこちらも合わせてお読みください。

// ユーザーIDをDBから非同期で取得する Selector 関数
const currentUserNameQuery = selector({
  key: "CurrentUserName",
  get: async ({ get }) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

const CurrentUserInfo = () => {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
};

// Suspenceを使って、
// Promiseが返却されるまでフォールバックを表示する
export const User = () => {
  <RecoilRoot>
    <Suspense fallback="読み込み中">
      <CurrentUserInfo />
    </Suspense>
  </RecoilRoot>;
};

TypeScriptへの対応

npmもしくはyarnで以下の型定義ファイルを導入すれば、TypeScriptでRecoilを使用できます。

npm install --save @types/recoil

TypeScriptの導入は決して必須ではありませんが、快適な開発環境を提供してくれるため導入することを強くオススメします。

Recoilでメモ帳アプリケーションを作ってみる

実際のサンプルとして、Recoil+TypeScriptでメモ帳のアプリケーションを作成しました。Hooks APIのuseRecoilState()などを使い、Atomsでステートを操作しています。

開発者ツール

2021年2月現在、Recoilにも有志の方が作成したRecoil Dev ToolsというRedux DevToolsのような開発者ツールが存在しています。

ただし、Recoilの仕様が変わればこの拡張機能も使用できなくなる可能性があることは念頭に置いておきましょう。

あくまで実験的なライブラリ

Recoilは2021年2月の段階ではまだ実験的なライブラリであるため、実際のサービスに導入するのは非常にリスクが高いといえます。Reactの基本理念を崩さず、状態管理をスマートに行えるという観点では非常に魅力的ではありますが、現段階では情報収集・手元での検証のみに留めておいたほうが安全と言えるでしょう。

まとめ

Reactの状態管理はさまざまな手法が存在しており、設計時に頭を悩ませる要因のひとつです。Reduxは定番の状態管理ライブラリとして根強い人気がありますが、スコープがグローバルになってしまうことが難点です。Recoilが正式にリリースされると、Redux一強だった状態管理のトレンドも少しずつ変わっていくのかもしれません。