😽

【React】Global State, Local Stateとは?Recoilを例に使い分けを紹介!

2024/09/03に公開

こんにちは!ヒロケイと申します。

皆さんは React の状態管理方法としてどんな方法を使っていますか?

今回は Local State と Global State の違いについて触れつつ、様々な状態管理方法についてまとめていきたいと思います。

React を学習している方のお役に立てれば幸いです。

Local State を使った状態管理

まず、Local State について見ていきましょう。

Local State は、コンポーネント内で状態管理をするための手法です。

useState を使った状態管理がまさにそれです。

Local State を使ったコードを見ていきましょう。

import { useState } from 'react';

function App() {
  const [inputValue, setInputValue] = useState('');
  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <p>入力値: {inputValue}</p>
    </div>
  );
}

export default App;

こちらは input を用意し、入力された値を表示するプログラムです。

  • inputValue: 管理させたい値

  • setInputValue: 値を更新させるときに使う関数

これら 2 つを使って

  • input 要素の onChange prop に setInputValue を設定(入力欄の更新とともに値も更新する)

  • 表示する p タグ内に inputValue を表示させる

するようにしています。

コンポーネントに分割する

次に、作成したプログラムをコンポーネントごとに分割していきましょう!

元々 App.jsx だけだったプログラムを Input.jsx と DisplayLabel.jsx に分けていきます。

App.jsx

import { useState } from 'react';
import DisplayLabel from './DisplayLabel';
import Input from './Input';

function App() {
  const [inputValue, setInputValue] = useState('');
  return (
    <div>
      <Input inputValue={inputValue} setInputValue={setInputValue} />
      <DisplayLabel inputValue={inputValue} />
    </div>
  );
}

export default App;

新しく Input と DisplayLabel コンポーネントが配置されています。

それぞれの props に state が渡され、inputValue と setInputValue が設定されていますね。

Input.jsx

import React from 'react';

const Input = (props) => {
  return (
    <div>
      <input
        type="text"
        value={props.inputValue}
        onChange={(e) => props.setInputValue(e.target.value)}
      />
    </div>
  );
};

export default Input;

props で渡ってきた値を input タグの

  • value→inputValue

  • onChange→setInputValue

に設定することで、入力欄の更新に伴って setInputValue を実行できます。

その結果、App.jsx の inputValue が更新されるわけですね。

DisplayLabel.jsx

import React from 'react';

const DisplayLabel = (props) => {
  return <div>入力値: {props.inputValue}</div>;
};

export default DisplayLabel;

こちらも同様に、proos で渡ってきた inputValue を表示させていますね。

このように Local State を使ってコンポーネントを分割する際、情報のやり取りに props を使わなければなりません。

ページ全体の規模が大きくなり、分割するコンポーネントの階層や数が増えてくると props のバケツリレーという事態を招くことになります。

props のバケツリレーのデメリットとしては

  • コードを読む際、props で渡ってきた情報のソースを探す時に時間がかかる

  • コンポーネントの props が増えてくるとコードが理解し辛くなる

こういった点で、props のバケツリレーを行うのは開発現場では避けるべきとされています。

      <RegisterPage
        isLoggedIn={isLoggedIn}
        isValidPassword={isValid}
        isAdminUser={isAdmin}
        maxNameLength={10}
        minNameLength={10}
        maxPasswordLength={10}
        minPasswordLength={10}
      />
      <LoginPage
        isLoggedIn={isLoggedIn}
        isValidPassword={isValid}
        isAdminUser={isAdmin}
        maxNameLength={10}
        minNameLength={10}
        maxPasswordLength={10}
        minPasswordLength={10}
      />
      .......
      // コンポーネントが増えれば増えるほどpropsの数も膨大になってしまう。。。

Global State を使った状態管理(Recoil)

それでは、props のバケツリレーを解決するためにはどうすれば良いのでしょうか?

そのためには、情報を親からではなく他の何処かから直接持ってくる方法が一番でしょう。

その何処かが Global State です。

Global State とは、アプリ全体を管理するためにの状態管理手法です。

以下のような情報を格納しておくときに効力を発揮します。

  • ユーザーがログインしているか?

  • 管理者ユーザーか?

  • 検索ページの入力内容

  • 通知

Global State を取り入れる手段はさまざまなものがありますが、今回は Recoil を使ってコードを改善していきたいと思います。

公式ドキュメント: https://recoiljs.org/docs/

App.jsx

import { RecoilRoot } from 'recoil';
import DisplayLabel from './DisplayLabel';
import Input from './Input';

function App() {
  return (
    <RecoilRoot> // RecoilRootでラップ
      <Input />
      <DisplayLabel />
    </RecoilRoot>
  );
}

export default App;

Recoil での状態管理を導入するには、管理下に起きたいコンポーネントを RecoilRoot でラップします。

アプリ全体を状態管理下におきたいので、App.jsx 内を RecoilRoot でラップしました。

また Local State はもう使わないので削除し、props には何も設定していない状態です。

form.js (New!)

import { atom } from 'recoil';

export const formState = atom({ // atom ≒ useState
  key: 'formState',
  default: '',
});

次に、管理したい状態を宣言します。

Recoil で状態を宣言する際には、atom()を使います。

Local State で言うところの useState()と似ていますね。

  • key: ユニークな文字列を設定。管理する状態のタイトルのようなもの

  • default: 初期値を設定する。文字列以外にも Int や Boolean も可

Input.jsx

import React from 'react';
import { useRecoilState } from 'recoil';
import { formState } from './form';

const Input = () => {
  const [inputValue, setInputValue] = useRecoilState(formState); // 引数には先ほど宣言したformState
  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
    </div>
  );
};

export default Input;

設定した Global State を使って Input.jsx を改良していきましょう。

useRecoilState を使って formState にアクセスします。

使い方は基本的に useState と同じなので、value や onChange に指定している記述はすぐ理解できると思います。

  • value: 管理下の状態

  • onChange: 状態を更新する関数

DisplayLabel.jsx

import React from 'react';
import { useRecoilState } from 'recoil';
import { formState } from './form';

const DisplayLabel = () => {
  const [inputValue] = useRecoilState(formState); // Input.jsxと同様の呼び出し
  return <div>入力値: {inputValue}</div>;
};

export default DisplayLabel;

Displaylabel に関しては setInputValue を呼び出していません。(使わないため)

両者の違い、使い分け

Local State, Global State に関して簡単に触れてきました。

Global State はとても便利ですが、管理する情報全てを Global にするべきではないです。

管理する情報によっては、一つのコンポーネント内でしか明らかに使わないようなものも存在します。

そういった管理対象がコンポーネント内で完結している状態は useState(Local State)を使うようにしましょう。

様々な Global State 管理方法

SWR

SWR は元々データフェッチのためのライブラリです。

しかし、ある応用をすることで Global State として使うことができるようになります。。。!

const { data, error } = useSWR('key', null, { initialData: 'light' });
// 第2引数(フェッチ関数)をnullにすることで、Global Stateとして応用できる!

参考になった記事

Redux

Redux は Global State の管理方法としてはメジャーなものです。

書き方や基本の理解に少々時間がかかるかもしれませんが、採用している会社も多いので学んでおいて損はないと思いますよ!

https://redux.js.org/

まとめ

最後まで読んでいただき、ありがとうございました。

React に限らず、こちらの記事では Node.js の理解を深められる講座を紹介しています。

https://zenn.dev/keigo_hirohara/articles/70b48086bfbd49

Discussion