はじめに
React Hooksがアナウンスされた時に、まずやってみようと思ったことが簡易なglobal stateの実現でした。React Hooks登場以前から試みていたのですが、それのHooks版を開発しました。
ライブラリ
https://github.com/dai-shi/react-hooks-global-state
このライブラリはとてもシンプルなものになっています。つまり、React ContextとReact Hooksを素直に使ったglobal stateになっています。
いくつかの特徴はありますが、あとで説明します。
使い方
まず必要なライブラリをimportします。
import React from 'react';
import { createGlobalState } from 'react-hooks-global-state';
最初にinitialStateを定義します。
const initialState = {
count: 0,
text: 'hello',
};
次に、それをもとにglobal stateを作成します。
const { GlobalStateProvider, useGlobalState } = createGlobalState(initialState);
global stateを使うコンポーネントを作ります。
const Counter = () => {
const [count, setCount] = useGlobalState('count');
return (
<div>
<span>Counter: {count}</span>
<button onClick={() => setCount(v => v + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
</div>
);
};
useGlobalState
の引数にglobal stateのproperty nameを指定しているところがポイントです。これを指定することで他のpropertyが変更にあった場合でも本コンポーネントは再renderする必要がなくなります。
最後にAppでProviderを指定します。
const App = () => (
<GlobalStateProvider>
<Counter />
<Counter />
</GlobalStateProvider>
);
デモ
CodeSandboxで動作させることができます。
特徴
React ContextとReact Hooksを使ってglobal stateを実現するライブラリは世の中にたくさんあります。本ライブラリはそのうちの一つですが、いくつかの特徴があります。
select by property name
global stateを設計する上でのポイントの一つは、大きなstateのうち、一部分を使う場合にそれを限定して使うための仕組みです。例えば、Reduxではselectorというインタフェースを使って、stateから派生したデータを作ります。本ライブラリはよりシンプルな手法として、stateオブジェクトのproperty nameでselectするという手法を採用しています。これは、selectorのようにオブジェクトの深い構造をselectすることはできませんが、reselectのようなselectorをmemoizeする必要がないというメリットがあります。その代わりstateオブジェクトはflatになるような設計にする必要はあります。同様のアプローチを採用しているライブラリにstoreonがあります。
unstable_observedBits
本ライブラリは上記selectの実現にobservedBitsという仕組みを使っています。observedBitsについてはこちらの記事が詳しいです。この仕組みを使うと、contextが変更した際に、一部のコンポーネントだけを再renderすることができます。
おわりに
本ライブラリは、名前が直接的なためか比較的参照されることが多いようですが、実は今後の方針は悩んでいます。一つは、今回紹介しなかったreducerインタフェースがReduxの完全な互換にはできないことと、もう一つは、observedBitsが将来のReactでは使えなくなる可能性が高いことがあります。ユーザの利用シーンをヒアリングしつつ今後の方針を決めていきたいと思います。