NU:LOGiC Logo

【React.js】Reactの新しい状態管理ライブラリ「Recoil」とは?createContext + useContextとの違いも解説!

【React.js】Reactの新しい状態管理ライブラリ「Recoil」とは?createContext + useContextとの違いも解説!

Reactには数多くのステート管理ツールがありますが、今回は「Recoil」について紹介します。この記事では基本的な使い方から、具体的な使用例、類似メソッドとの比較を添えて解説していきます!

3行で要約すると

  • RecoilはReactのグローバルステート管理ライブラリで、atomselectorを中心に効率的なステート管理が可能。
  • RecoilとcreateContextを比較すると、Recoilは非同期処理のサポートや洗練されたAPIでのパフォーマンス最適化が強み。
  • 使用時の注意点としては、atomselectorのキーの一貫性、過度な再レンダリング、非同期のエラーハンドリングが挙げられる。

「Recoil」の基本的な使い方

「Recoil」とは?

Recoilは、Reactでのグローバルなステート管理をシンプルにするためのライブラリです。伝統的なContext APIやReduxとは異なり、Recoilはアトミックなデータフローの管理を可能にします。基本的に、Recoilは「atom」と「selector」の2つの主要な部分から成り立っています。最もポピュラーな使い方として、グローバルなステートを「atom」として定義し、それをReactコンポーネントで簡単に取得・更新することが挙げられます。
例えば、以下のコードは、グローバルなカウンターステートをRecoilを使って管理する方法を示しています。

import { atom, useRecoilState } from 'recoil';

// グローバルステートの定義
const counterState = atom({
  key: 'counterState', // unique ID (with respect to other atoms/selectors)
  default: 0, // default value
});

function CounterComponent() {
  const [count, setCount] = useRecoilState(counterState);

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

このコードでは、atomを使用してカウンターのステートを定義し、useRecoilStateを使ってそのステートを取得・更新しています。このように、Recoilを使用することで、シンプルかつ効率的にグローバルステートを管理することができます。

「Recoil」の使用例3パターン

使用例1: TODOリストのステート管理

Recoilを使って、TODOリストのアイテムをグローバルステートとして管理する方法を考えてみましょう。この場合、各TODOアイテムは独自のステートを持ち、全体のリストもまた別のステートとして扱われます。

import { atom, useRecoilState } from 'recoil';

const todoListState = atom({
  key: 'todoListState',
  default: [],
});

function TodoComponent() {
  const [todoList, setTodoList] = useRecoilState(todoListState);

  const addTodo = (task) => {
    setTodoList([...todoList, task]);
  };

  // ...
}

使用例2: テーマカラーのトグル

ユーザーがアプリのテーマカラーをダークモードとライトモードで切り替えることを考えます。Recoilを使って、このテーマカラーのステートを管理する方法は以下の通りです。

import { atom, useRecoilState } from 'recoil';

const themeState = atom({
  key: 'themeState',
  default: 'light', // or 'dark'
});

function ThemeToggleComponent() {
  const [theme, setTheme] = useRecoilState(themeState);

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  // ...
}

使用例3: ユーザープロフィールの取得と更新

Recoilを使用してユーザープロフィールの情報をグローバルに管理する方法を検討します。例えば、ユーザー名やメールアドレスなどの情報を取得・更新する機能を提供できます。

import { atom, useRecoilState } from 'recoil';

const userProfileState = atom({
  key: 'userProfileState',
  default: { username: '', email: '' },
});

function ProfileComponent() {
  const [profile, setProfile] = useRecoilState(userProfileState);

  const updateProfile = (newProfile) => {
    setProfile(newProfile);
  };

  // ...
}

これらの例から、Recoilを使用すると、さまざまなタイプのグローバルステートを簡単に管理できることがわかります。

「createContext」と「useContext」を用いたグローバルステート管理の違い

createContextuseContextフックは、Reactの組み込みの機能で、グローバルまたはコンポーネントツリー全体でアクセス可能なステートや関数を提供する方法を提供しています。これを使ってグローバルステート管理のシステムを構築することも可能です。では、RecoilcreateContextを用いたグローバルステート管理はどう違うのでしょうか?

1. 定義と取得の柔軟性

  • Recoil: atomを用いてステートの定義を行い、それを任意のコンポーネントで取得・更新することができます。これにより、ステートの取得や更新が非常に直感的です。
import React from 'react';
import { RecoilRoot, atom, useRecoilState, selector } from 'recoil';

// ステートの定義
const countState = atom({
  key: 'countState',
  default: 0,
});

// 派生ステートの例
const doubleCountState = selector({
  key: 'doubleCountState',
  get: ({ get }) => {
    return get(countState) * 2;
  },
});

function Counter() {
  const [count, setCount] = useRecoilState(countState);
  const doubleCount = useRecoilValue(doubleCountState);

  return (
    <>
      <p>Count: {count}</p>
      <p>Double Count: {doubleCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
}

function App() {
  return (
    <RecoilRoot>
      <Counter />
    </RecoilRoot>
  );
}

export default App;
  • createContext + useContext: プロバイダ(Provider)をコンポーネントツリーのルート近くでラップし、そのコンテキストを子コンポーネントで消費することでステートや関数を共有します。
import React, { createContext, useContext, useState } from 'react';

const CountContext = createContext();

function useCount() {
  const context = useContext(CountContext);
  if (!context) {
    throw new Error('useCount must be used within a CountProvider');
  }
  return context;
}

function Counter() {
  const { count, setCount } = useCount();

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
}

2. 再レンダリングの最適化

  • Recoil: 特定のatomまたはselectorに依存するコンポーネントのみが、そのデータが変更されたときに再レンダリングされます。これは、大規模なアプリケーションでのパフォーマンスを向上させるのに役立ちます。
import { atom, selector, useRecoilValue } from 'recoil';

const countState = atom({
  key: 'countState',
  default: 0,
});

const doubleCountState = selector({
  key: 'doubleCountState',
  get: ({ get }) => {
    return get(countState) * 2;
  },
});

function DisplayDoubleCount() {
  const doubleCount = useRecoilValue(doubleCountState);
  return <p>Double Count: {doubleCount}</p>;
}
  • createContext + useContext Contextの値が変わると、そのContextを消費するすべてのコンポーネントが再レンダリングされる可能性があります。ただし、ReactのmemouseMemoを適切に使用することで、不要な再レンダリングを避けることも可能です。

3. 派生ステートの管理

  • Recoil: selectorを使用して、他のatomまたはselectorから派生したステートを簡単に作成できます。
  • createContext + useContext: 派生ステートは手動で管理しなければならず、独自のロジックやメモ化を使用して更新を制御する必要があります。

4.非同期操作の統合

  • Recoil: selectorを使用して非同期操作をステートの一部として扱うことができます。
  • createContext + useContext: 非同期操作の結果をステートとして管理するには、外部ライブラリやuseReducerと組み合わせて実装する必要があります。

まとめると、Recoilはグローバルステートの管理に特化しており、多くの高度な機能と最適化を提供しています。一方、createContextはもともとはステートの共有や関数の伝搬のために設計されており、グローバルステート管理のための完全なソリューションとしては設計されていません。しかし、それでも簡単なケースや特定の要件には十分適しています。

「Recoil」を使用する際の注意点

Recoilは非常に便利で、多くの高度な機能を提供していますが、適切に使用しないと問題が生じる場面もあります。以下は、Recoilを使用する際の一般的な注意点と、その問題を避けるための方法を示したものです。

atomselectorのキーの一貫性

Recoilのatomselectorを定義する際、それぞれに一意のキーを割り当てる必要があります。このキーの名前付けの一貫性を保つことが重要です。異なる場所で同じキー名を使用してしまうと、不具合や意図しない動作が発生する可能性があります。

// 良い例
const countState = atom({
  key: 'countState',  // キーが明確で一貫している
  default: 0,
});

// 悪い例
const countState = atom({
  key: 'userState',  // このキー名は意味が一致していない
  default: 0,
});

過度な再レンダリングを避ける

Recoilは変更を監視して再レンダリングを効率的に行いますが、大きなアプリケーションや多くのatomselectorを持つ場合、パフォーマンスの問題が発生する可能性があります。不要な再レンダリングを避けるために、派生ステートや依存関係を適切に管理することが重要です。

// 適切に依存関係を管理する例
const filteredTodos = selector({
  key: 'filteredTodos',
  get: ({ get }) => {
    const filter = get(todoFilterState);
    const todos = get(todoListState);

    switch (filter) {
      case 'Completed':
        return todos.filter((todo) => todo.completed);
      // 他の条件...
    }
  },
});

非同期の取り扱い

Recoilのselectorを使用して非同期処理を行う際、エラーハンドリングを適切に行うことが重要です。特に外部APIを呼び出す際などは、ネットワークのエラーやタイムアウトなど、さまざまな問題が発生する可能性があります。

const userData = selector({
  key: 'userData',
  get: async ({ get }) => {
    try {
      const response = await fetch('/api/user');
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    } catch (error) {
      throw error;
    }
  },
});

これらの注意点を理解し、適切に取り扱うことで、Recoilを効果的に利用することができます。

「Recoil」の使い方まとめ

Recoilは、Reactアプリケーションでのグローバルステート管理を簡単に行うためのライブラリです。この記事では、Recoilの基本的な使い方や主要な特徴、そしてRecoilの注意点について詳しく解説しました。

  • 基本的な使い方: atomselectorを使用してステートや派生ステートを定義し、useRecoilStateuseRecoilValueなどのフックを使ってコンポーネントからアクセスできるようにします。

  • 使用例: カウンターアプリケーションやTODOリストのようなシンプルなアプリから、非同期データの取得や処理など、さまざまな使用例を通じてRecoilの強力な機能を体感できます。

  • RecoilとcreateContextの違い: 両者ともにグローバルステート管理のためのツールですが、Recoilはより洗練されたAPIや非同期処理のサポート、パフォーマンス最適化など、多くの強みを持っています。

  • 注意点: atomselectorのキーの一貫性、過度な再レンダリング、非同期の取り扱いなど、Recoilを使用する際に知っておくべき注意点があります。

Recoilを使うことで、Reactアプリケーションのステート管理を効率的に、そして簡単に行うことができます。この記事を参考に、Recoilを使ったアプリケーション開発を楽しんでください!