当サイトは、アフィリエイト広告を利用しています
ReactHooksのuseRefの使い方について調べてみた。
useRefは値を変更しても再レンダリングされない点、useStateと異なる。
※useStateは値を更新すると再レンダリングされる。
使用頻度としては個人的にはuseStateの方が多いが、実現したい
画面の動作によってはuseRefを使うこともあるので
についてまとめておく
useStateについては下記記事でまとめていますのでよければ見てください
useRefとはDOMの要素と変数を紐づけて、
その変数を使って紐づいたDOM要素の値を参照、変更したり
またDOM操作を可能にするhook。
import "./styles.css";import React, { useState, useRef } from "react";export default function App() {// stateconst [text, setText] = useState("初期値");// useRefconst sampleRef = useRef(null);// ボタン押下処理const confirm = () => {// inputの値をstateに設定(画面に出力される)setText(sampleRef.current.value);};return (<div><input type="text" ref={sampleRef} /><button onClick={confirm}>決定</button><div>{text}</div></div>);}
inputの入力値をuseRefの.currentプロパティ経由で取得できる
uesRefとuseStateを比較すると以下の点が異なる
useState等はstateの更新時に再レンダリングが発生するが
useRefは発生しない。
そのため裏側ではuseRefの値は更新されているが画面には反映されない
import "./styles.css";import React, { useRef } from "react";export default function App() {const inputRef = useRef({});const outLog = () => {console.log(inputRef.current.value);};return (<div className="App"><span>入力</span><input type="text" ref={inputRef} /><div><div><span>useRefの値 : </span>{inputRef.current.value}</div></div><button onClick={() => outLog()}> ログ出力</button></div>);}
値が同期的に更新されるため、inputに入力した時点で裏では
useRefは更新されているため、ログ出力するとinputに入力した値が出力される。
※再レンダリングはされてないため画面には未反映
import "./styles.css";import React, { useState, useRef } from "react";export default function App() {const [state, setState] = useState();const inputRef = useRef({});const outLog = () => {console.log(inputRef.current.value);};return (<div className="App"><span>入力</span><input type="text" ref={inputRef} /><div><div><span>useRefの値 : </span>{inputRef.current.value}</div></div><button onClick={() => outLog()}> ログ出力</button><button onClick={() => setState(inputRef.current.value)}>再レンダリング</button></div>);}
useRefを使ったサンプルをいつか書いてみる
useRefを使ってDOMの値の参照や変更、focusなどの操作を
行うことができる。
angularのng-modelやvue.jsのv-modelのようなイメージだが
useRef内の値は即時反映されるが、画面には再レンダーされるまでは
反映されない。
import "./styles.css";import React, { useState, useRef } from "react";export default function App() {// stateconst [text, setText] = useState("初期値");// useRefconst prevCountRef1 = useRef(null);const prevCountRef2 = useRef(null);// ボタン押下処理const confirm = () => {// inputの値をstateに設定(画面に出力される)setText(prevCountRef1.current.value);// DOMの対する参照// 二つ目のinputへのフォーカスprevCountRef2.current.focus();// 値に対する参照// input1の値をinput2に反映prevCountRef2.current.value = prevCountRef1.current.value;};return (<div><input type="text" ref={prevCountRef1} /><button onClick={confirm}>決定</button><div><span>state:</span><span>{text}</span></div><span>input1の値を反映:</span><input id="ddd" type="text" ref={prevCountRef2} /></div>);}
useRefとuseStateを組み合わせてstateの前回の値をuseRefで保持させる。
ポイントとしてはuseEffectが実行されるのはstateのcountが変更されて
画面のレンダリングが終わった後だということ。
import "./styles.css";import React, { useState, useRef, useEffect } from "react";function Counter() {// stateconst [count, setCount] = useState(0);// useRefconst prevCountRef = useRef();// useEffect// countが変更された時のレンダリング後に動く// つまり画面ではbefourは一つ前の値が出力されている// 裏ではprevCountRefの値自体は次の値に更新されている。useEffect(() => {prevCountRef.current = count;console.log(prevCountRef.current)}, [count]);//console.log(prevCountRef.current)return (<h1>Now: {count}, before: {prevCountRef.current}{/*Increment */}<button onClick={() => setCount((count) => count + 1)}>Increment</button></h1>);}export default function App() {return (<div className="App"><Counter /></div>);}
下記のような流れになるため
画面表示では一つ前の値が出力されているが実際のprevCountRefの値はstateの値と同じに
更新されている
useEffectにクリーンアップ処理を加えて、実行順序を確認する。
import "./styles.css";import React, { useState, useRef, useEffect } from "react";function Counter() {// stateconst [count, setCount] = useState(0);// useRefconst prevCountRef = useRef();// useEffect// countが変更された時のレンダリング後に動く// つまり画面ではbefourは一つ前の値が出力されている// 裏ではprevCountRefの値自体は次の値に更新されている。useEffect(() => {prevCountRef.current = count;console.log("effect")// クリーンアップ処理を追加return ()=>console.log("clean")}, [count]);// 起動メッセージconsole.log("start")return (<h1>Now: {count}, before: {prevCountRef.current}{/*Increment */}<button onClick={() => setCount((count) => count + 1)}>Increment</button></h1>);}export default function App() {return (<div className="App"><Counter /></div>);}
初期表示時間
state更新時
useStateとuseRefと使ってDOMの変更を即時反映ではなく、
何かしらのevent(ボタン押下など)で行いたい場合は
useStateとuseRefの両方を使うことで実現できる。
※下記で実装するサンプルはCSS-in-JSのEmotionを使用します。
Emotionについては下記記事で紹介しているので参照ください
useStateではstateの値が変わった時に再レンダリングされるため
値は即時反映になる
下記はinputに入力したEmotionで色に文字を変更するサンプル
/** @jsxImportSource @emotion/react */import { css } from "@emotion/react";import { useState } from "react";// useStateのみで行うconst UseStateComponent = () => {const [color, setColor] = useState("skyblue");return (<div css={[container(true)]}><h3>useStateを使うver</h3><div>存在する色を入力してください</div><divcss={[css`color: red;`]}>(即時反映)</div><div><inputtype="text"value={color}onChange={(e) => setColor(e.target.value)}/></div><div css={[container(false)]}><h1 css={[colorStyle(color)]}>hello Emotion</h1></div></div>);};export default UseStateComponent;const container = (line) => [css`display: grid;grid-template-columns: 300px;justify-content: center;border: ${line ? "1px solid black" : ""};margin: 10px;`];const colorStyle = (color) => [css`color: ${color};`];
即時反映ではなく、eventで反映させたい場合は
useStateとuseRefを両方使う。
仕組みとしては
になる。
useRefが「値が変更されても再レンダリングしない」ことと
useStateが「値が変更タイミングでレンダリングする」ことを
利用している。
/** @jsxImportSource @emotion/react */import { css } from "@emotion/react";import { useState, useRef } from "react";const UseRefComponent = () => {const [color, setColor] = useState("skyblue");// useRefconst sampleRef = useRef(null);// ボタン押下処理const confirm = () => {// inputの値をstateに設定(画面に出力される)setColor(sampleRef.current.value);};return (<div css={[container(true)]}><h3>useRefを使うver</h3><div>存在する色を入力してください</div><divcss={[css`color: red;`]}>(ボタン押下で反映)</div><div><input type="text" ref={sampleRef} /><button onClick={confirm}>反映</button></div><div css={[container(false)]}><h1 css={[colorStyle(color)]}>hello Emotion</h1></div></div>);};export default UseRefComponent;const container = (line) => [css`display: grid;grid-template-columns: 300px;justify-content: center;border: ${line ? "1px solid black" : ""};margin: 10px;`];const colorStyle = (color) => [css`color: ${color};`];
上記の
を動作確認したソースを下記に載せときます!
複数refを一度に作りたい場合は
import "./styles.css";import React, { useState, useRef } from "react";export default function App() {const [word, setWord] = useState("なし");const list= ["りんご", "みかん", "ぶどう"];// list個数分の空ref配列を作成するconst refs = useRef(list.map(() => React.createRef()));return (<div className="App"><p>クリックしたボタン:{word}</p>{/* 空ref配列を回して、refを紐づけながらbuttonをつくる */}{refs.current.map((ref, i) => (<buttonref={ref}key={i}onClick={() => {if (ref.current) {setWord(ref.current.innerText);}}}>{list[i]}</button>))}</div>);}