Help us understand the problem. What is going on with this article?

useRef は何をやっているのか

useRef は元々あった createRef の hooks 版です。その名の通り、DOMに対する参照を持つために使われるのが主な目的です。

ですがそれ以外の用途にも利用することができます。

最初に私が知ったのはこちらのReact本体のドキュメントにも記されている「以前のstateの値を参照する方法」です
https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state

function usePrevious(value: any) {
  const ref = useRef(null);
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

なにやら useRef で前回の値を保持しているようですね。それまで ref と言えばDOMに対して使うものと思っていたので「え、useRefってDOM以外の値を参照するのに使えるの?」と驚きました。驚きましたが当時の私はとりあえずスルーしました。

次に出会ったのがこちらの記事のこの節。

https://www.codebeast.dev/react-memoize-hooks-useRef-useCallback-useMemo/#memoizing-with-useref

特に気になったのが次の useRef の特徴です。

  1. Stores data
  2. Does not cause re-render when the data it stores changes
  3. Remembers its stored data even after state change in useState causes a re-render.

気になったものの、この記事を読んだ当時の私は useRef の詳細については華麗にスルーしました。

ですが日々開発していて、たま〜〜〜に useRef をDOM以外に使ったテクを見かけるので、今回深ぼろうと思った次第です。

なのでこの記事では次のようなことを書きます。

  • useRef は具体的になにをしているのか
  • どんなユースケースが考えられるか

useRef は具体的になにをしているのか

実際の実装を見てみましょう。

まずは初期化の処理から。

function mountRef<T>(initialValue: T): {|current: T|} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  if (__DEV__) {
    Object.seal(ref);
  }
  hook.memoizedState = ref;
  return ref;
}

https://github.com/facebook/react/blob/dd7e5e4f5ac2ffac3171ef61daee2cb1edc69635/packages/react-reconciler/src/ReactFiberHooks.js#L1170

単純に useRef() の引数に入ってきた値を代入しているだけですね。なにも難しいことはないです。次に初期化以降の再描画の時を見てみましょう。

function updateRef<T>(initialValue: T): {|current: T|} {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

これだけです。少な過ぎてこの記事が企画倒れになる気もしましたが、強い気持ちで書き進めます。このコードを読む限り、 useRefは再描画が行われても単純にメモ化された値を返すだけです。

明示的に値を変えたい場合は

const hoge = useRef(null)

const handleSomething = () => {
  hoge.current = "new value"
}

のように明示的に current というプロパティに代入するしかありません。

ここから直感的に冒頭にあった「Does not cause re-render when the data it stores changes」がなんとなく腹落ちした方もいらっしゃるかもしれません。どういうことかと言いますと useRef で保持した値を変える時はただの値の代入でしかないため state を変えるわけではありません。このため再描画が発生しないのです。そしてグローバルな hooks の領域に値が保存されているため、再計算が行われても値は失われません。

どんなユースケースが考えられるか

useRef で値を保持することの特性は 「再描画を発生させないこと」 これに尽きます。

なのでユースケースとしては

  • 描画には関係のない状態

を扱うために使うのが主となるでしょう。

usePrevious の例は分かりやすいですね。前回の値を保持しておいて、それをコンポーネント内の関数やらの分岐に使うわけです。ただ正直これ以外は私はあまりユースケース思いつかないです。多分ノリ的にはClassコンポーネント時代に、Classのプロパティとして this.xxx という風に扱っていたものと同じな気がするんですが、それもあまりやった覚えがないのでいい具体例が出てこない…。

ただ覚えておいて損はない特性だと思うので、この記事がいつか役に立てると幸いです。

seya
最近の趣味はGraphQLとFigmaです。
https://note.mu/seyanote
linc-well
Linc'well(リンクウェル)は2018年創業のヘルスケアスタートアップです。我々は、医療のIT化を通じて、人々と社会の健康に貢献します。
https://www.linc-well.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away