56
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React HooksAdvent Calendar 2019

Day 17

React.useRef()を使って無駄なレンダリングを減らそう

Last updated at Posted at 2019-12-16

はじめに

本記事は、ReactのuseRefについて紹介する入門的記事です。公式に書いてある内容の焼き直しみたいな物なので、ちょっとrefについて理解が怪しいかな?と思う方が読者対象になります。

React refの基本的な使い方・登場シーン

さて、refの主な登場シーンとしてはDOMに紐付けて使う場合が多いのではないでしょうか。

inputとref(CodeSandbox)

Focus Inputボタンを押すとinputエリアにフォーカスします。

const App = () => {
  const textInputRef = useRef();
  return (
    <div className="App">
      <h1>useRef with Text Input</h1>
      <button onClick={() => textInputRef.current.focus()}>Focus Input</button>
      <input ref={textInputRef} type="text" />
    </div>
  );
};

videoとref(CodeSandbox)

初回レンダリング時にvideo srcが設定され、videoはmp4ファイルを再生できる状態でレンダリングされます。

const App = () => {
  const videoRef = useRef();
  useEffect(() => {
    videoRef.current.src =
      'https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_10mb.mp4';
  }, []);
  return (
    <div className="App">
      <h1>useRef with Video</h1>
      <video ref={videoRef} controls width={360} height={240} />
    </div>
  );
};

DOMと組み合わせるだけがrefの全てではない(おおげさ)

ここからが、記事タイトルについての中身になります。
useState()はlocal stateを管理できるHooksですが、useState()で用意したstate変数の更新用関数を呼ぶと、その時点で再レンダリングが走ります。以下で言うところのsetState関数が更新用関数ですね。

const [state, setState] = useState();

そのため、再レンダリングが必要ない値の管理にuseState()を使うと無駄なレンダリングが走ることになってしまいます。(シンプルな処理だと実害は少ないかと思います)

そこでuseRef()です。
APIドキュメントFAQにもあるように、useRef()で生成したref変数は値を保持し続けるオブジェクトとして利用できます。

const ref = useRef();

生成されたref変数は{ current: ... }というオブジェクトを与えられ、このref.currentを自由に書き換えることが許されているのですが、これを書き換えた際には再レンダリングが走らないという特性を持ちます。(特性というか単なるJSのオブジェクト変数の書き換えなのですが)
なので、先に述べたように再レンダリングは必要なく値のみを変更して保持するために使えたり、コンポーネントの前後のレンダリング間で値を保持し続けたい場合に、useRef()が使えます。
ということが本記事の伝えたいことになります。なんと。まぁ、公式にも書いてあるんですが。

useState()とuseRef()の比較

最後に、似たような記述内容を書いてuseState()とuseRef()を見比べてみます。
まずはuseState()の場合です。
以下のコードは無限にレンダリングが起こります。
1回目のレンダリング処理でsetText('hoge');が呼ばれると、空文字からhogeへtext変数が更新されて再レンダリングが走ります。そして、それ以降もレンダリングの度にsetText('hoge');が呼ばれるので無限レンダリングとなります。

const Text = () => {
  const [text, setText] = useState('');
  setText('hoge');
  return <div>{text}</div>;
}

次に、useRef()を同じような記述構成で書いてみます。
結果は、1回のレンダリングのみでhogeが表示されます。

const Text = () => {
  const textRef = useRef('');
  textRef.current = 'hoge';
  return <div>{textRef.current}</div>;
}

特に意味のないコードですが、理解のための例でした。

おわりに

公式ドキュメントのおさらいでしたが、APIの用途を知っておくと今後の開発でも役に立つと思います。
是非、無駄なレンダリングが走ってる場面に遭遇したり、レンダリング間で値を保持しい場面に遭遇したらuseRef()のことを思い出してみてください。

56
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
56
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?