unstated-nextとは
unstated-nextはReactのContext, Hooksのみでの状態管理をサポートする最小限なライブラリ。
めちゃ小さい。
Context + hooksのみで状態管理
useStateを使ってstateとその増減操作する機能を持ったカスタムフックを作成。
useCounter
import { useState } from 'react';
const useCounter = () => {
const [count, setCount] = useState(0);
const decrement = () => setCount(count - 1);
const increment = () => setCount(count + 1);
const reset = () => setCount(0);
return { count, decrement, increment, reset }
}
export {
useCounter,
};
それを使う側、
App.tsx
import React, { createContext } from 'react';
import { useCounter } from './hooks';
import NonUnstatedCounter from './components/NonUnstatedCounter';
import ResetButtonN from './components/NonUnstatedCounter/ResetButton';
export const CounterContext = createContext({
count: 0,
decrement: () => {},
increment: () => {},
reset: () => {}
});
const App: React.FC = () => {
const counter = useCounter();
return (
<div className="contents">
<h1>Context + hooks</h1>
<CounterContext.Provider value={counter}>
<NonUnstatedCounter />
<NonUnstatedCounter />
<ResetButtonN />
</CounterContext.Provider>
</div>
);
}
export default App;
createContext カスタムフックであるuseCounterをcreateContextで作成したContextのProvider.valueに入れて、カスタムを扱いたいコンポーネントを包む。
中のコンポーネントでは、
Counterコンポーネント
import React, { useContext } from 'react';
import { CounterContext } from '../../App';
const NonUnstatedCounter = () => {
const {
count,
decrement,
increment,
} = useContext(CounterContext);
return (
<div className="counterN">
<span className="decrement" onClick={decrement}>-</span>
<span className="countNum">{count}</span>
<span className="increment" onClick={increment}>+</span>
</div>
);
};
export default NonUnstatedCounter;
ResetButtonコンポーネント
import React, { useContext } from 'react';
import { CounterContext } from '../../App';
const ResetButtonN = () => {
const {
reset,
} = useContext(CounterContext);
return (
<span className="resetButton" onClick={reset}>RESET</span>
);
};;
export default ResetButtonN;
reactのuseContextを使って親で作成したCounterContextを渡してContextを利用する形になる。
unstated-nextで書き換え
useCounter
import { useState } from 'react';
import { createContainer } from 'unstated-next'; // <-- unstated-next
const useCounter = () => {
const [count, setCount] = useState(0);
const decrement = () => setCount(count - 1);
const increment = () => setCount(count + 1);
const reset = () => setCount(0);
return { count, decrement, increment, reset }
}
const CounterContainer = createContainer(useCounter); // unstated-nextのcreateContainerで包む
export {
useCounter,
CounterContainer, // <-- unstated-next
};
unstated-nextのcreateContainerでカスタムフックをラップしてexportしている。
App.tsx
import React from 'react';
import { CounterContainer } from './hooks';
// import NonUnstatedCounter from './components/NonUnstatedCounter';
// import ResetButtonN from './components/NonUnstatedCounter/ResetButtonN';
import UnstatedCounter from './components/UnstatedCounter';
import ResetButton from './components/UnstatedCounter/ResetButton';
// いらない子
// export const CounterContext = createContext({
// count: 0,
// decrement: () => {},
// increment: () => {},
// reset: () => {}
// });
const App: React.FC = () => {
// const counter = useCounter(); // <-- いらない子
return (
<div className="contents">
{/* <>
<h1>Context + hooks</h1>
<CounterContext.Provider value={counter}>
<NonUnstatedCounter />
<NonUnstatedCounter />
<ResetButtonN />
</CounterContext.Provider>
</> */}
<>
<h1>unstated-next</h1>
<CounterContainer.Provider>
<UnstatedCounter />
<UnstatedCounter />
<ResetButton />
</CounterContainer.Provider>
</>
</div>
);
}
export default App;
createContextを使ったり、Providerにvalueを渡す必要がない。
Counterコンポーネント
import React from 'react';
import { useContainer } from 'unstated-next';
import { CounterContainer } from '../../hooks';
const UnstatedCounter = () => {
const {
count,
decrement,
increment,
} = useContainer(CounterContainer);
return (
<div className="counterN">
<span className="decrement" onClick={decrement}>-</span>
<span className="countNum">{count}</span>
<span className="increment" onClick={increment}>+</span>
</div>
);
};
export default UnstatedCounter;
ResetButtonコンポーネント
import React from 'react';
import { useContainer } from 'unstated-next'; // <-- unstated-next
import { CounterContainer } from '../../hooks';
const ResetButton = () => {
const {
reset,
} = useContainer(CounterContainer); // <-- unstated-next
return (
<button className="resetButton" onClick={reset}>reset</button>
);
};;
export default ResetButton;
Containerを使う側のコンポーネントでは、unstated-nextのuseContainerでカスタムフックを使う。
ContextをContainerに置き換えたイメージ。
なんとなくの利点としては、最小限・最適な粒度でState(Container)を管理していけば、パフォーマンスの改善が見込めるのではないかと感じました。