3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React】useStateとuseRecoilでの状態管理の違い

Last updated at Posted at 2023-06-27

はじめに

useStateとuseRecoilでの状態管理の違いについて雑にまとめてみました。

ざっくりと

  • useStateはコンポーネント内で状態管理することができる
  • useRecoilは複数のコンポーネント間で状態管理することができる

親子関係にあるコンポーネント間で状態管理を共有したい場合、useStateを使うとpropsを渡す必要があるけれどuseRecoilを使うとpropsを渡さなくていいよということになります。

例題として以下2つのコンポーネントをChatgptに考えてもらいました。

  • 所持金の増減ができるMannyApp.tsx
  • 所持金の2倍やリセットができるOtherComponent.tsx

MannyApp.tsxを親コンポーネント、OtherComponent.tsxを子コンポーネントとします。

これらをtop画面にまとめて表示させたものがこちらです。

image.png

増やすボタンや2倍にするボタンを押したときの違いを見ていきたいと思います。

useState

親コンポーネント

所持金をbalanceという変数で状態管理し、後述の子コンポーネントに渡しています。

MannyApp.tsx
import { Box, Button } from '@chakra-ui/react'
import React, { useState } from 'react'
import { balanceState } from '../atoms/state';
import { useRecoilState } from 'recoil';
import OtherComponent from './OtherComponent';

export const MoneyApp = () => {
    const [balance, setBalance] = useState(0);

    const increaseBalance = () => {
        setBalance(balance + 1);
    };

    const decreaseBalance = () => {
        setBalance(balance - 1);
    };

  return (
    <div>
        <Box>親コンポーネント</Box>
        <Box>親コンポーネントの所持金:{balance}</Box>
        <Button onClick={increaseBalance}>増やす</Button>
        <Button onClick={decreaseBalance}>減らす</Button>
        <OtherComponent balance={balance}/>
    </div>
  )
}

子コンポーネント

親コンポーネントからpropsで受け取ったbalanceを状態管理しています。

OtherComponent.tsx
import React, { useEffect, useState } from 'react';
import { Box, Button } from '@chakra-ui/react';

type Props = {
    balance: number
}

const OtherComponent = (props: Props) => {
    const { balance } = props;
    const [otherBalance, setOtherBalance] = useState(balance);

    useEffect(() => {
        setOtherBalance(balance);
      }, [balance]);
    
  const doubleBalance = () => {
    setOtherBalance(otherBalance * 2);
  };

  const resetOtherBalance = () => {
    setOtherBalance(0);
  };

  return (
    <div>
      <Box>子コンポーネント</Box>
      <Box>親コンポーネントの所持金: {balance}</Box>
      <Button onClick={doubleBalance}>2倍にする</Button>
      <Button onClick={resetOtherBalance}>リセット</Button>
      <Box>子コンポーネントの所持金: {otherBalance}</Box>
    </div>
  );
};

export default OtherComponent;

初期値にbalanceを指定しているので、
親の所持金を増やすと、子コンポーネントでの親と子の所持金にも増分が反映されていますね。
image.png

2倍にすると子コンポーネントの所持金だけ更新されていますね。
これはuseStateを使っているため、子コンポーネントで管理している状態を親コンポーネントが検知できないからです。

image.png

リセットも同様です。

image.png

このようにuseStateを使用する場合は親から子にpropsを渡し、それぞれのコンポーネント内で状態管理を行う必要があります。子コンポーネントで更新した状態を親コンポーネントで参照しないのであればこれで問題ないです。
親コンポーネントでも参照したい場合はuseRecoilを使う必要があります。

useRecoil

useRecoilを使用するとコンポーネント間で状態管理を共有することができます。
つまりpropsの受け渡しが不要になり、どのコンポーネントでbalanceの状態を参照しても同じ値となります。

まずはrecoilを導入します。

$ yarn add recoil

忘れずにindex.tsxのAppをRecoilRootで囲います。

index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { ChakraProvider } from '@chakra-ui/react';
import { RecoilRoot } from 'recoil';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <ChakraProvider>
      <RecoilRoot>
        <App />
      </RecoilRoot>
    </ChakraProvider>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

atomsを定義します。

state.tsx
import { atom } from 'recoil';

export const balanceState = atom<number>({
  key: 'balanceState',
  default: 0,
});

親コンポーネント

useStateの代わりにuseRecoilStateを使ってbalanceの状態管理をしています。
このコンポーネントでbalanceの状態が更新されたとき、
他のコンポーネントでuseRecoilStateを使用すると更新されたbalanceを参照することができます。

MannyApp.tsx
import { Box, Button } from '@chakra-ui/react'
import { balanceState } from '../atoms/state';
import { useRecoilState } from 'recoil';
import OtherComponent from './OtherComponent';

export const MoneyApp = () => {
    const [balance, setBalance] = useRecoilState(balanceState)

    const increaseBalance = () => {
        setBalance(balance + 1);
    };

    const decreaseBalance = () => {
        setBalance(balance - 1);
    };

  return (
    <div>
        <Box>親コンポーネント</Box>
        <Box>親コンポーネントの所持金:{balance}</Box>
        <Button onClick={increaseBalance}>増やす</Button>
        <Button onClick={decreaseBalance}>減らす</Button>
        <OtherComponent />
    </div>
  )
}

子コンポーネント

useStateと違い、propsを受け取っていません。
useRecoilStateを使ってグローバルにbalanceの状態を取得しています。

OtherComponent.tsx
import { useRecoilState } from 'recoil';
import { balanceState } from '../atoms/state';
import { Box, Button } from '@chakra-ui/react';

const OtherComponent = () => {
      const [balance, setBalance] = useRecoilState(balanceState);

  const doubleBalance = () => {
    setBalance(balance * 2);
  };

  const resetOtherBalance = () => {
    setBalance(0);
  };

  return (
    <div>
      <Box>子コンポーネント</Box>
      <Box>親コンポーネントの所持金: {balance}</Box>
      <Button onClick={doubleBalance}>2倍にする</Button>
      <Button onClick={resetOtherBalance}>リセット</Button>
      <Box>子コンポーネントの所持金: {balance}</Box>
    </div>
  );
};

export default OtherComponent;

親の所持金を増やすとuseState同様に子の所持金も更新されます。

image.png

しかし子の所持金を2倍にしたりリセットするとuseStateのときとは違い、親の所持金にも更新が反映されています。
これは子コンポーネントと親コンポーネント間で状態管理の共有ができているからです。

image.png

おわりに

useRecoilすごいです。

3
1
0

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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?