Posted at

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/master//packages/react-reconciler/src/ReactFiberHooks.js#L848

単純に 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 という風に扱っていたものと同じな気がするんですが、それもあまりやった覚えがないのでいい具体例が出てこない…。

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