コンテキストほどがっつりした仕組みでなく、単に共通の値置き場として使うフックを考えます。このフックにはプロバイダーがなく単にフックを呼ぶだけで呼び場所に関わらず同じ参照の変数を提供します。例えば次です。
const ComponentA = () => {
const {x, setX} = useHogePageState();
return (
<div>
<div>Component A</div>
<input onChange={e=>setX(e.target.value)}>
<div>{x}</div>
</div>
)
}
/**
* このコンポーネント上で値 x を変えると ComponentA 上で表示される値も変わる
*/
const ComponentB = () => {
const {x, setX} = useHogePageState();
return (
<div>
<div>Component B</div>
<input onChange={e=>setX(e.target.value)}>
<div>{x}</div>
</div>
)
}
これはシングルトンパターンで実現できます。シングルトンパターンとは”あるクラスのインスタンスが絶対に1個しか存在しない”状態を作るコーディングのパターンです。ここでは React 上で扱う状態を表現するクラスを作り、それをシングルトンとして扱います。そうすることによってそのクラスインスタンスとやり取りするフックはコンポーネントを跨いでも共通の値を取り扱う様になります。
これは次のコードで実現できます。
import React from "react";
/**
* 状態を表現するシングルトンインスタンス
*/
class HogePageState {
constructor() {
this._x = 1;
// 状態に代入が起きた時、同時に回す関数を入れる箱
this._setStateInterceptors = {};
}
get x() {
// 単にインスタンスの持つ値を返す
return this._x;
}
set x(val) {
this._x = val;
// 代入時、外から渡された関数を全て実行
for (let key in this._setStateInterceptors) {
this._setStateInterceptors[key](val);
}
}
addSetStateInterceptor(id, func) {
// 代入時に実行される関数を追加
this._setStateInterceptors[id] = func;
}
deleteSetStateInterceptor(id) {
// 代入時に実行される関数を除去
delete this._setStateInterceptors[id];
}
}
const HogePageStateSingleton = new HogePageState();
export const useHogePageState = () => {
// シングルトンインスタンスを呼び出し
const state = HogePageStateSingleton;
// React に値が書き換わったことを示すために useState の setter を使用
const [, setReactX] = React.useState(state.x);
// マウント時に シングルトンインスタンスの値書き換え時に useState の settter が動作する関数を定義
const id = Math.random();
React.useEffect(() => {
state.addSetStateInterceptor(id, (newVal) => setReactX(newVal));
}, []);
// アンマウント時に 取り付けた関数を除去
React.useEffect(() => {
return () => {
state.addSetStateInterceptor(id, (newVal) => setReactX(newVal));
};
}, []);
// シングルトンインスタンスの値を使う変数、関数を出力
return {
x: state.x,
setX: (newX) => (state.x = newX)
};
};
実際に動いているデモが次です。
例ではクラスとフックを一ファイルにまとめていますが複数ファイルに分けてもシングルトンの振る舞いをさせられます。そうするためにはインスタンスを export します。export は時点までに評価された結果を export 先から参照する仕組みです。このためインスタンスを export した場合、どの import からでも同じインスタンスを参照することになります。
export – JavaScript | MDN