はじめに
useStateとuseRecoilでの状態管理の違いについて雑にまとめてみました。
ざっくりと
- useStateはコンポーネント内で状態管理することができる
- useRecoilは複数のコンポーネント間で状態管理することができる
親子関係にあるコンポーネント間で状態管理を共有したい場合、useStateを使うとpropsを渡す必要があるけれどuseRecoilを使うとpropsを渡さなくていいよということになります。
例題として以下2つのコンポーネントをChatgptに考えてもらいました。
- 所持金の増減ができるMannyApp.tsx
- 所持金の2倍やリセットができるOtherComponent.tsx
MannyApp.tsxを親コンポーネント、OtherComponent.tsxを子コンポーネントとします。
これらをtop画面にまとめて表示させたものがこちらです。
増やすボタンや2倍にするボタンを押したときの違いを見ていきたいと思います。
useState
親コンポーネント
所持金をbalanceという変数で状態管理し、後述の子コンポーネントに渡しています。
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を状態管理しています。
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を指定しているので、
親の所持金を増やすと、子コンポーネントでの親と子の所持金にも増分が反映されていますね。
2倍にすると子コンポーネントの所持金だけ更新されていますね。
これはuseStateを使っているため、子コンポーネントで管理している状態を親コンポーネントが検知できないからです。
リセットも同様です。
このようにuseStateを使用する場合は親から子にpropsを渡し、それぞれのコンポーネント内で状態管理を行う必要があります。子コンポーネントで更新した状態を親コンポーネントで参照しないのであればこれで問題ないです。
親コンポーネントでも参照したい場合はuseRecoilを使う必要があります。
useRecoil
useRecoilを使用するとコンポーネント間で状態管理を共有することができます。
つまりpropsの受け渡しが不要になり、どのコンポーネントでbalanceの状態を参照しても同じ値となります。
まずはrecoilを導入します。
$ yarn add recoil
忘れずにindex.tsxのAppをRecoilRootで囲います。
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を定義します。
import { atom } from 'recoil';
export const balanceState = atom<number>({
key: 'balanceState',
default: 0,
});
親コンポーネント
useStateの代わりにuseRecoilStateを使ってbalanceの状態管理をしています。
このコンポーネントでbalanceの状態が更新されたとき、
他のコンポーネントでuseRecoilStateを使用すると更新されたbalanceを参照することができます。
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の状態を取得しています。
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同様に子の所持金も更新されます。
しかし子の所持金を2倍にしたりリセットするとuseStateのときとは違い、親の所持金にも更新が反映されています。
これは子コンポーネントと親コンポーネント間で状態管理の共有ができているからです。
おわりに
useRecoilすごいです。