🌟

【React】状態管理でuseContextとRecoilを試してみた。

2022/11/26に公開

概要

Reactにおける状態管理といえば、通常親コンポーネントから子コンポーネントにデータを渡す際はpropsを介して行います。ただ階層が深くなるごとに、バケツリレーのように受け渡していく方法では、管理が大変になってしまい、それを解決するために、ReactにはuseContextというReactHooksがあります。

今回は、RecoilというMeta社(旧Facebook)が開発しているReactの状態管理ライブラリらしく昨年から??あったのかシンプルで使いやすそうなのでちらっと見てみた。

https://recoiljs.org/

useContextとRecoilとの違いは?

そうなってくると気になるのがuseContextとRecoilとの違いです。

useContext

こちらはReactから提供されているReact純正のReactHooks。
基本的な使い方はuseContextもRecoilも似てるなーと思っていましたが、大きな違いはuseContextのでstate管理した場合は再レンダリングされる。

Recoil

こちらはReact同様にMetaが提供しているので、ほかのライブラリよりは安心??要素がおおいと思われる。
また学習コストもuseContext同様に基本的な使い方は時間もかからないと思われる。
useContextとの違いは不要な再レンダリングを発生させずに値を更新するらしい。
とは言っても設計次第でしょうか。

実際に試してみる。

今回はとりあえずuseContextとRecoilを比べてみたいので、カウンターを作成してみる。
使い方の詳細はまた今度。

下記のように子コンポーネントと孫コンポーネントなど階層が分かりやすくするためにCSSでボーダーで囲ってます。

  • 親コンポーネント(Parent)
  • 子コンポーネント(Child)
  • 孫子コンポーネント(GrandChild)

親コンポーネントにあるボタンをクリックしたら孫コンポーネントで、カウンターの数字を表示してみる。子コンポーネント(Child)は孫コンポーネントまで階層を深くするためだけなので、意味はないです。

useContextの場合

とりあえずすべてのコード

App.js
import "./App.css";

function App() {
  return (
    <>
      <Parent />
    </>
  );
}
export default App;
Parent.js
import Child from './components/Child';
import { createContext, useState } from 'react';

//createContext()でコンテキストオブジェクトを生成します。
export const sampleContext = createContext();

function Parent() {
  const [countNum, setCounter] = useState(0)
  const counterFunc=()=>{
    setCounter((prevCount) => prevCount + 1)
  }
  
  return (
    <div>
      <h1>親の階層:App</h1>
      <p>一番親の階層からボタンをクリックすると孫のコンポーネントの数値が変わります。</p>
      <button onClick={counterFunc}>カウンター</button>
      
     //Childコンポーネントをコンテキストオブジェクトで囲んで
     //Provider経由でvalueに管理したい値を設定
     <sampleContext.Provider value={countNum}>
        <Child />
      </sampleContext.Provider>
    </div>
  );
}
export default Parent;
Child.js
import GrandChild from './GrandChild'

const Child = () => {
  return (
    <div>
      <h1>子コンポーネント:Child</h1>
      
      //GrandChildコンポーネントを呼び出す
      <GrandChild />
    </div>
  )
}
export default Child
GrandChild.js
import { useContext } from "react";
import { sampleContext } from "../App";

const GrandChild = () => {
 //Appで作成したコンテキストオブジェクトをimportしてuseContextに渡す
 const context = useContext(sampleContext);
 
  return (
    <div>
      <h1>孫コンポーネント:GrandChild</h1>
      <p>親の階層にあるボタンをクリックしたらカウンターが更新される</p>
      
      //context受け取った値を表示
      <p>カウンター:{context}</p>
     
    </div>
  );
};

export default GrandChild;

Recoilの場合

useContextの場合、コンテキストオブジェクトで囲みましたが、Recoilの場合も同様にRecoilRootで囲みます。

App.js
import Parent from "./components/Parent";
import { RecoilRoot } from "recoil";

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

export default App;

Recoilの場合、管理したいデータはAtomというオブジェクト??を使います。
Reduxのようにアプリケーション全体で状態管理を行うストアが一つなのに対し、
RecoilはAtom単位で一つ一つの状態管理を行うみたい。
Atomで指定するkeyは他と被らないようにユニークにする必要があり、defaultはそのまま初期値を設定。

また、Ricoilの場合useStateの代わりにuseRecoilStateを使います。
注意点としてRecoilRootの中で使用しないとエラーがでます。
その為、ParentコンポーネントをRecoilRootで囲みますが、Parentコンポーネントより下層のコンポーネントでないとエラーがでます。

Parent.js
import React from 'react'
import Child from './Child';
import { useRecoilState,atom} from 'recoil';

//RicoilのAtomで管理したいデータを作成
//他でも使うのでexportしておく
export const countAtom = atom({
  key: "countAtom",
  default: 0,
});

const Parent = () => {
  const [count, setCounter] = useRecoilState(countAtom)
  const counterFunc = () => {
    setCounter((prevcon) => prevcon + 1);
  };
  return ( 
  <> 
  <h1>Parent</h1>
    <button onClick = {counterFunc} > カウンター < /button>
    <Child / >
  </>
    )
}
export default Parent

ちなみに状態管理したいファイルを外部にしてimportするのもよい。

countAtom.js
import {atom} from 'recoil';
//RicoilのAtomで管理したいデータを作成
//他でも使うのでexportしておく
export const countAtom = atom({
  key: "countAtom",
  default: 0,
});

ChildコンポーネントはuseContextの時と同じ、GrandChildコンポーネントを呼び出すだけなので省きます。

GrandChildでは状態管理しているカウントの数字を表示させるのでcountAtomをimportします。
またRicoilの場合useStateではなくuseRicoilStateを使うといったが、数字だけを表示させたい場合はuseRecoilValueというメソッドがある。

GrandChild.js
import { useRecoilValue } from "recoil";
import { countAtom } from "./Parent";

const GrandChild = () => {

  const countnum = useRecoilValue(countAtom);
  return (
    <div style={{ border: "1px solid #ff0000", padding: "10px" }}>
      <h1>孫コンポーネント:GrandChild</h1>
      <p>親の階層にあるボタンをクリックしたらカウンターが更新される</p>
      <p style={{ fontSize: "20px", fontWeight: "bold" }}>
        カウンター:{countnum}
      </p>
    </div>
  );
};

export default GrandChild;

上記でuseContextとRicoilの両方で状態管理を試してみた。

まとめ

という事でRecoilを使ってみたけど、簡単で使いやすいですね。
以下、参考にさせていただいたページです。

https://reffect.co.jp/react/react-usecontext-understanding#useContext
https://zenn.dev/web_tips/articles/851557606491f3
https://reffect.co.jp/react/react-recoil#React
https://zenn.dev/kyo9bo/articles/58b0ef35837462
https://topaz.dev/recipes/2cfdccb9086f2fcc5e3c
https://www.happylifecreators.com/blog/20220415/

Discussion