Reactでアプリケーション開発を行う際、useStateを使ってcomponentを生成する場合はそのcomponentがUnmount(画面でそのcomponentを使用しなくなったとき)したとき、stateをメモリリークしないようにしないと宜しくないですよね。
実際、開発ツールでもconsoleでwarning表示されます。
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
ではUnmountしたときにstateをメモリリークしない書き方について。
classComponentとhooksの書き方
いまのReactのバージョンで開発する際はhooksで構築するかと思います。
その場合、Unmount時に行う処理はuseEffectを使用してそのなかで実装します。
useEffect(() => {
return () => {
// Unmount時の処理を記述
};
}, []);
classComponentのときはライフサイクルメソッドの「componentWillUnmount」内で記述します。
componentWillUnmount() {
// Unmount時の処理を記述
}
stateを解放させる便利関数
ここからはhooksで開発する前提で話を進めていきます。
先ほど説明したComponentごとのuseEffect内にUnmout時にstateをメモリーリークしない記述をしていくのでもいいのですが、大規模な開発になっていくつものComponentに同じような記述をしていくとそれはそれで面倒になってきます。
なので、helper関数を作って処理を簡素化させるのが宜しいかと思われます。
あらゆる書き換え可能な値を保持しておくのに便利なhook「useRef」を使います。
この関数でUnmount時にunmountRef.current
がtrueになります。
const useUnmountRef = () => {
const unmountRef = useRef(false);
useEffect(
() => () => {
unmountRef.current = true;
},
[]
);
return unmountRef;
};
そのuseUnmountRefとuseStateを融合させたhelper関数を作ります。
const useSafeState = (unmountRef, defaultValue) => {
const [state, changeState] = useState(defaultValue);
const wrapChangeState = useCallback(
(v: any) => {
if (!unmountRef.current) {
changeState(v);
}
},
[changeState, unmountRef]
);
return [state, wrapChangeState];
};
Unmountしてないときだけstateが書き換えられるようにしています。
Component内で実装
const unmountRef = useUnmountRef();
const [state, setState] = useSafeState(unmountRef, {stateの初期値});
これだけで済みます。
なんとも便利な関数ですね。
また、Unmount時の処理はこの書き方になります。
if (unmountRef.current) {
// Unmount時の処理
}
たとえばblob画像を生成して、その画像を解放させるときrevokeObjectURL()
関数を使いますが、その箇所に記述すると宜しいかと思われます。