yukinaka[log]

React useRef を理解する

React16.8で追加されたhooks APIのうち、useRefについて説明します。

DOMノードへの参照

まず最初に、useRefはクラスコンポーネントで使用されていたReact.createRef()と同じく、DOMノードへの参照を保持することができます。

公式

import React, { useRef } from 'react';

const App: React.FunctionComponent = () => {
  const inputRef = useRef<HTMLInputElement>(null)
  const handleClick = () => inputRef.current?.focus();

  return (
    <>
      <input type="text" ref={inputRef} />
      <button onClick={() => handleClick()} >ボタン</button>
    </>
  )
}

useRefはミュータブルなrefオブジェクトを返す関数です。この例では、const inputRef = useRef<HTMLInputElement>(null) の部分で要素を参照するためのrefオブジェクトを作成しています。

次に<input type="text" ref={inputRef} /> の部分でref属性にinputRefを渡しているため、DOMへの参照が得られ、button要素をクリックした際にinput要素にfocusするというDOMの操作ができるようになっています。

これが基本的な使用方法の一つです。

再描画されない変数

また、useRef()はref属性からDOMへの参照を求める以外にも使用方法があります。クラスでインスタンス変数を使用する場合と同じように、ミュータブルな値を維持するのに便利です。useRefは、そのcurrentプロパティに変更可能な値を保持できる箱のようなものとしてイメージしてください。前述のパターンはReactがrefを通して自動でcurrentプロパティにDOMノードをセットしています。

以下のサンプルを参考に解説します。

import React, { useRef, useState } from "react";

const App: React.FunctionComponent = () => {
  let count: number = 0;
  const countRef = useRef<number>(0);
  const [state, update] = useState<string>("state");

  const handleClick = () => {
    count++;
    countRef.current++;
    update(`${state}-update`);
  };

  return (
    <div>
      <div>カウント1: {count}</div>
      <div>カウント2: {countRef.current}</div>
      <div>state: {state}</div>
      <button onClick={() => handleClick()}>ボタン</button>
    </div>
  );
};

export default App;

まず、letで宣言したcountと、useRefを用いて作成したcountRefの二種類を用意しています。

ボタンをクリックすると、stateが更新され、コンポーネントの再描画が起こるために、handleClickの中でcount++;の処理があるにも関わらず、常に初期化されてしまい、画面に表示されるカウント1の数字は0となってしまいます。

一方でcountRefは再描画されないため、countRef.current++;の意図通り、数字が+1ずつされていきます。

useref-render

前の状態を参照する

useRefの応用で以前のstateを取得することもできます。(How to get the previous props or state?)

import React, { useRef, useState, useEffect } from "react";

const App = () => {
  const [count, setCount] = useState<number>(0);
  const prevCountRef = useRef<number>(0);
  const handleClick = () => {
    setCount(prevCountRef.current + 1);
  };

  useEffect(() => {
    prevCountRef.current = count;
  });

  return (
    <>
      <h1>
        Now: {count}, before: {prevCountRef.current}
      </h1>
      <button onClick={() => handleClick()}>ボタン</button>
    </>
  );
};
export default App;

prevCountRefとして以前のstateの入れ物を作っています。ボタンをクリックすると再描画され、countの値は更新されます。useEffectは再描画が終了した後に毎回動作しますが、currentプロパティを更新しても再描画は発生しないため、prevCountRef.currentの中身は一つ前の状態のものが表示されています。

get the previous state?

まとめ

  • useRefはDOMノードへの参照ができる
  • useRefはミュータブルなrefオブジェクトを返しているだけ
  • currentプロパティにはミュータブルな値を維持できる(コンポーネントの再描画は行われない)
  • React
© 2020