useRefといえば、DOMにアクセスする手段としておなじみ ですが、なんとなく使い所がわかったようなわからないような気がしたのでメモを残しておきます。
1秒間に1回、useStateを使ってtextを更新するコードを書いた際、下記の書き方だとコンソールには'hello'が表示され続けます。
const [text, setText] = React.useState('hello'); const textRef = React.useRef(text); React.useEffect(() => { setInterval(() => { console.log(text); // => hello setText(String(Date.now())); }, 1000); }, []);
なぜならば、useStateは、再レンダー間で同一性が保たれ、変化しないことを保証されているからです。
consoleに更新されたtextを表示したい場合は、下記のような書き方になります。
const [text, setText] = React.useState('hello'); const textRef = React.useRef(text); React.useEffect(() => { setInterval(() => { console.log(text); // => hello setText(String(Date.now())); }, 1000); }, []); React.useEffect(() => { console.log(text); }, [text]);
ほとんどの場合はこれでOKなのですが、のっぴきならない理由で、
const [text, setText] = React.useState('hello'); React.useEffect(() => { setInterval(() => { console.log(text); // ← ここの部分 setText(String(Date.now())); }, 1000); }, []);
ここの部分で最新のtextを表示しなければならないとしましょう。(そんなケースはないと思いますが)
そんなときは、useRefを使えば一応実現できます。
const [text, setText] = React.useState('hello'); const textRef = React.useRef(null); React.useEffect(() => { setInterval(() => { console.log(textRef.current); setText(String(Date.now())); }, 1000); React.useEffect(() => { textRef.current = text; }, [text]); }, []);
なぜならば、useRefで返されるオブジェクトは、コンポーネントの存在期間全体にわたって存在し続けるからです。また、useRefの値を更新してもコンポーネントに再レンダリングが走らないこともポイントです。
DEMO
こちらの例を見るとuseStateの値は不変、useRefの値は変動していることがわかります。
肝心の使い所ですが、
・コンポーネントの存在期間全体にわたって存在し続けることができる
・コンポーネントの再レンダリングが走らない
の特性を活かせるポイントがなかなか見つからずでしたが、直近だと、localStorageに書き込む値(画面には表示しない)の管理に使ってみました。
なぜならば、useStateで管理すると、画面には表示しないにもかかわらず再レンダリングが走ってしまうからです。
かなり特殊な例だと思いますし、きっとベストプラクティスではないんだと思います。
今後も使い所を探してみます。