当サイトは、アフィリエイト広告を利用しています

【ReactHooks】useRefの使い方~useStateと使い分け~

作成日:2022月03月15日
更新日:2024年02月13日

ReactHooksのuseRefの使い方について調べてみた。
useRefは値を変更しても再レンダリングされない点、useStateと異なる。
※useStateは値を更新すると再レンダリングされる。

使用頻度としては個人的にはuseStateの方が多いが、実現したい
画面の動作によってはuseRefを使うこともあるので

  • useRefの使い方
  • useStateとの違い
  • useStateとuseRefの併用

についてまとめておく
useStateについては下記記事でまとめていますのでよければ見てください

useRefとは?

useRefとはDOMの要素と変数を紐づけて、 その変数を使って紐づいたDOM要素の値を参照、変更したり
またDOM操作を可能にするhook。

useRefの基本構文

  • useRefを初期化する(refObject)が設定させる
  • 任意のDOM要素にrefで紐づける
  • 紐づけることで.currentプロパティにDOMの情報を保持するようになる
jsx
import "./styles.css";
import React, { useState, useRef } from "react";
export default function App() {
// state
const [text, setText] = useState("初期値");
// useRef
const 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の違い

uesRefとuseStateを比較すると以下の点が異なる

  • uesRefを更新しても再レンダリングされない
  • 値が同期的を更新される

uesRefを更新しても再レンダリングされない

useState等はstateの更新時に再レンダリングが発生するが
useRefは発生しない。
そのため裏側ではuseRefの値は更新されているが画面には反映されない

jsx
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に入力してログ出力した時、ログにはinputの値が出力されるが、画面には反映されない。

値が同期的を更新される

値が同期的に更新されるため、inputに入力した時点で裏では
useRefは更新されているため、ログ出力するとinputに入力した値が出力される。
※再レンダリングはされてないため画面には未反映

jsx
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>
);
}
  • input入力後、再レンダリングを押下し、stateを更新して再レンダリングさせれば画面に反映される

参考

useRefを使った値管理ガイド

useRefを使ったサンプル

useRefを使ったサンプルをいつか書いてみる

DOMを参照、操作する

useRefを使ってDOMの値の参照や変更、focusなどの操作を
行うことができる。
angularのng-modelやvue.jsのv-modelのようなイメージだが
useRef内の値は即時反映されるが、画面には再レンダーされるまでは
反映されない。

jsx
import "./styles.css";
import React, { useState, useRef } from "react";
export default function App() {
// state
const [text, setText] = useState("初期値");
// useRef
const 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>
);
}

stateの前の値を保持させる

useRefとuseStateを組み合わせてstateの前回の値をuseRefで保持させる。
ポイントとしてはuseEffectが実行されるのはstateのcountが変更されて
画面のレンダリングが終わった後だということ。

jsx
import "./styles.css";
import React, { useState, useRef, useEffect } from "react";
function Counter() {
// state
const [count, setCount] = useState(0);
// useRef
const 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>
);
}

下記のような流れになるため

  1. countが更新される
  2. 画面がレンダリングされる(画面のbeforeには更新前のprevCountRefの値が表示される)
  3. useEffectでprevCountRefが更新される

画面表示では一つ前の値が出力されているが実際のprevCountRefの値はstateの値と同じに
更新されている

クリーンアップ処理をuseEffectに加えた場合

useEffectにクリーンアップ処理を加えて、実行順序を確認する。

jsx
import "./styles.css";
import React, { useState, useRef, useEffect } from "react";
function Counter() {
// state
const [count, setCount] = useState(0);
// useRef
const 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>
);
}
  • 初期表示時間

    1. 画面レンダリングに前に「start」を出力する
    2. countに初期値0が設定されたため、画面レンダリング後に「effect」が出力される
  • state更新時

    1. countが更新され、再レンダリングされるときに「start」が出力される
    2. 画面レンダリング後にクリーンアップ処理で「clean」が出力される
    3. クリーンアップ処理後に「effect」が出力される。

useStateとuseRefを併用するサンプル

useStateとuseRefと使ってDOMの変更を即時反映ではなく、
何かしらのevent(ボタン押下など)で行いたい場合は
useStateとuseRefの両方を使うことで実現できる。
※下記で実装するサンプルはCSS-in-JSのEmotionを使用します。 Emotionについては下記記事で紹介しているので参照ください

useStateのみを使用して実装した場合

useStateではstateの値が変わった時に再レンダリングされるため
値は即時反映になる
下記はinputに入力したEmotionで色に文字を変更するサンプル

UseStateComponent.jsx
/** @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>
<div
css={[
css`
color: red;
`
]}
>
(即時反映)
</div>
<div>
<input
type="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};
`
];
  • inputに値を入力した時に即時に文字の色が変更される

useStateとuseRefを使用して実装した場合

即時反映ではなく、eventで反映させたい場合は
useStateとuseRefを両方使う。
仕組みとしては

  • inputをuseRefで紐づける
  • button押下でuseRefからuseStateに反映させる

になる。
useRefが「値が変更されても再レンダリングしない」ことと
useStateが「値が変更タイミングでレンダリングする」ことを
利用している。

UseRefComponent.jsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { useState, useRef } from "react";
const UseRefComponent = () => {
const [color, setColor] = useState("skyblue");
// useRef
const sampleRef = useRef(null);
// ボタン押下処理
const confirm = () => {
// inputの値をstateに設定(画面に出力される)
setColor(sampleRef.current.value);
};
return (
<div css={[container(true)]}>
<h3>useRefを使うver</h3>
<div>存在する色を入力してください</div>
<div
css={[
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};
`
];
  • ボタン押下時に文字に色が反映される。

実行確認

上記の

  • useStateのみを使用して実装した場合
  • useStateとuseRefを使用して実装した場合

を動作確認したソースを下記に載せときます!

useRef_useState_sample

参考

useStateとuseRefの違い
useRefの使い道

複数refを作る

複数refを一度に作りたい場合は

  • ラムダのmap
  • React.createRef を使って作ることができる
jsx
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) => (
<button
ref={ref}
key={i}
onClick={() => {
if (ref.current) {
setWord(ref.current.innerText);
}
}}
>
{list[i]}
</button>
))}
</div>
);
}
  • 空ref配列を作る
  • 空ref配列をループし、画面の項目と紐づけながら描画する

参考

複数のref

新着記事

タグ別一覧
top