React Hooks で追加されたuseRef
は最初は使いどころがよく分からなかったのですが、実践例を見つつサンプルを書いてみたら使いどころが掴めてきたのでまとめます。
前提
ReactのuseState
とuseEffect
の使い方は覚えたけどuseRef
についてはあまり公式例を見てもイメージがつかない人向けです。
サンプルコードはnpx create-react-app my-app --template typescript
で作成して、App.tsx
に書いています。
https://create-react-app.dev/docs/adding-typescript
.current を利用して初回レンダーのuseEffect()を早期returnで終了させる
useRefには.current
プロパティがありますが、これはコンポーネントのレンダー時に毎回セットした初期値を返します。
Reactにとってのインスタンス変数のように扱えます。
この性質を利用して初期描画時にフラグを立てて、useEffectのマウント時の初回実行を早期returnで終了させることができます。
これによってStateが更新された時のみuseEffectに設定した関数を実行、というのが実現できます。
下記の例はFooコンポーネント内のstateが更新された時のみuseEffectに設定した関数を実行します。 ブラウザ上のconsole画面を見ながらボタンを押していくと分かりやすいです。
import React, { useState, useRef, useEffect } from "react"; function App() { return ( <div> <Foo /> </div> ); } function Foo() { const [value, setValue] = useState(0); const isFirstRender = useRef(true); const addValue = () => { setValue(value + 1); }; const sameValue = () => { setValue(value); }; useEffect(() => { if (isFirstRender.current) { isFirstRender.current = false; return; } console.log("updated"); }, [value]); return ( <> <h3>{`Now state : ${value}`}</h3> <button onClick={addValue}>+1</button> <button onClick={sameValue}>don't change</button> </> ); } export default App;
isFirstRender.current
による初回実行時の早期returnがない場合、初回マウント時にconsole.log("updated");
が実行されてしまいます。
useEffect(() => { // block exec when first render if (isFirstRender.current) { isFirstRender.current = false; return; } console.log("updated"); }, [value]);
createRefのHooks版として使用してrefオブジェクトにアクセスする
これは公式でも触れられていたものです。これはcreateRef()
でも実現できます。
https://ja.reactjs.org/docs/hooks-reference.html#useref
ただ、Hooks時代のReactから入門した人からするとこの説明だけだとfocus()だけなのか?になるので、深掘りして行きます。
公式ドキュメントのRef と DOMの項を見るとこう書いてあります。
いつ Ref を使うか
Ref に適した使用例は以下の通りです。
- フォーカス、テキストの選択およびメディアの再生の管理
- アニメーションの発火
- サードパーティの DOM ライブラリとの統合
https://ja.reactjs.org/docs/refs-and-the-dom.html
フォーカス、テキストの選択とあるのでサンプルコードを書いてみます。
この例ではinputタグの値をコピーしてクリップボードに保存する処理になります。
ここではtargetRef.current
のオブジェクトはHTMLInputElement型なので、HTMLInputElementのオブジェクトが使えます。
import React, { useRef } from "react"; function App() { const targetRef = useRef<HTMLInputElement>(null); const copyFromRef = () => { if (targetRef) { targetRef.current?.focus(); targetRef.current?.select(); document.execCommand("copy"); targetRef.current?.blur(); } }; return ( <div> <button onClick={copyFromRef}>Copy input value</button> <input ref={targetRef} type="text" defaultValue="copy to clipboard" /> </div> ); } export default App;
例えば、会員登録の入力フォームにて、ボタンを押したら自動的に最初の入力項目にフォーカスするようにしてみたい場合を考えます。 その場合はPropsとしてRefオブジェクトを受け取ってフォーカスするボタンコンポーネントを設計した方が、取り回しが良いと思います。 その場合のサンプルコードは下記です。TypeScriptでの型情報を加えると下記の通りです。
import React, { useRef } from "react"; function App() { const targetInputRef = useRef<HTMLInputElement>(null); return ( <div> <FocusButton target={targetInputRef} /> <input ref={targetInputRef} type="text" defaultValue="focus" /> </div> ); } interface FocusButtonType { target: React.RefObject<HTMLInputElement> } function FocusButton(props: FocusButtonType) { const focusToTarget = () => { if(props.target) { props.target.current?.focus() } } return ( <button onClick={focusToTarget}>Focus!</button> ) } export default App;
アニメーションの発火の例では、こちらが参考になります。
CSSアニメーションの制御を行っています。
PIXIV TECH FES.のLPを支えるCSSアニメーションテクニック
https://inside.pixiv.blog/2020/01/21/180000
まとめ
最初にuseRefを見たときはユースケースが思い浮かばず、必要性を感じなかったのですが、実践例を見てイメージがついて行きました。
createRef()
でできたことは置き換えられるみたいなので、Hooks時代から入った人はその辺りを見ながら参考にするといいかもしれません。
参考リンク
Make React useEffect hook not run on initial render
https://stackoverflow.com/questions/53253940/make-react-useeffect-hook-not-run-on-initial-render
useRef は何をやっているのか
https://qiita.com/uhyo/items/246fb1f30acfeb7699da#useref
HTMLInputElement(MDN)
https://developer.mozilla.org/ja/docs/Web/API/HTMLInputElement