LoginSignup
1
0

More than 1 year has passed since last update.

React 公式の Ref の記事を簡潔にまとめました:DOM操作編

Last updated at Posted at 2022-04-04

Manipulating the DOM with Refs

前回の続きです。
今回は ref による DOM操作に関する記事です。

refがよく使われるケース

  • ノードにフォーカスする、ノードまでスクロールする、ノードのサイズやポジションを測定するなど

使い方

<div ref={myRef}> のようにしてDOMに ref を移植するだけです。
これでDOMに対して 以下のようなAPI を使用して操作が可能

例えば、myRef.current.scrollIntoView()でそのノードまでスクロールできます。

自作のコンポーネントに ref を使う場合

<input ref={inputRef} /> のような一般的なDOMに対してブラウザから操作する要素にrefを指定するのは問題ありません。

一方で自分で作ったコンポーネントの場合、下記の例のように何も考えずに <MyInput ref={inputRef} /> のようにすると inputRef.current.focus() はエラーになります。

import { useRef } from 'react';

function MyInput(props) { // ここで refを受け取れていない
  return <input {...props} />; // このDOMに ref を渡さないといけない
}

export default function MyForm() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus(); // エラーになる
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

原因は<myInput /><input>をラップしていて、この状態では ref を子に渡せていないからです。
デフォルトでは子コンポーネントにrefを渡すようになっていません。

そうした場合は、下記のようにforwardRef()を使って記述することで子に ref を渡すことができ、これによって操作が可能になります。

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

ref の操作を制限する

また、子に渡したrefを使うと、さまざまなAPIが使えてしまいますが、以下のように
useImperativeHandle() を使うことで、ref の操作を限定することができます。
下の例では使用できるAPIをfocus()のみに限定しています。

const MyInput = forwardRef((props, ref) => {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    // Only expose focus and nothing else
    focus() {
      realInputRef.current.focus();
    },
  }));
  return <input {...props} ref={realInputRef} />;
});

ステートと ref の変更が反映されるまでの時間の違い

ステートの変更は待ち行列で処理され、即時に評価されません。
一方で ref はすぐに評価されるため、ステートの処理より先に評価されてしまいます。
そのため、以下のようなコードは一番最後の要素にスクロールされません。

setTodos([ ...todos, newTodo]);
listRef.current.lastChild.scrollIntoView();

処理されるタイミングを合わせるには

上記のような問題に対処するには flushSync を使います。
ステートの変更を待つように指定することができるのです。
以下の例ではコードは一番最後の要素にスクロールできます。

import flushSync from react-domして

flushSync(() => {
  setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView();

ref の注意点

Reactで管理されているDOMに ref の使用はできるだけ避けた方がよいそうです。
特に子要素を持つnodeには注意。
しかし、全く操作してはいけないというわけではないそうです。

1
0
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
1
0