【React学習】〜useRef〜

React

 Reactではレンダリングという考え方が重要で、レンダリングによって厄介なことが起こります。

参照

 JavaScriptそのものを学ぶUdemy講座で、変数の参照(メモリのどこに格納しているのかという情報)について学習しました。(React関連の講座でもだいたい「参照」については説明されていると思います)

var a = 1;
var b = a;

 例えばこのように変数を宣言したとします。このときメモリには「a」という変数の値を格納する領域と「b」という変数の値を格納する領域が確保されます。ここで、a=2と書き換えてみると、aの値だけが2となります。

var obj1 = { a:1 };
var obj2 = obj1;
obj2.a = 2;

 ただし、変数に格納するものによっては、異なる挙動となります。例えばオブジェクトを格納したときには、その「参照」が格納されます。2行目で新しくobj2というオブジェクト(obj1と同じ中身を持つ)を宣言しており、3行目でobj2のaプロパティの値を2に変更しています。このとき実は、obj1のaプロパティも2に書き換わってしまうんです。

 なぜなら、obj2に格納されているのがobj1の中身ではななく、obj1の「参照」だからです。つまり、obj1とobj2はメモリの中の同じ場所を見ているわけです。なので、obj2のプロパティを変更したつもりでも、参照先の中身が書き換わるため、obj1のプロパティも変わった(というように見える)のです。

レンダリングと参照

 Reactのコンポーネントは関数で定義され、レンダリング(実行)時に変数を格納するメモリ領域を確保します。つまり、同じ変数名だとしても、レンダリングするごとに別のメモリ領域に変数を格納してしまうんです。そうするとレンダリングによって変数の「参照」が変わってしまうことになるわけです。

 大抵の場合は参照先が変わったとしても、それを元に処理を実行するので問題ないんですが、子コンポーネント内の情報を親が読み取りたいときや、DOM(Document Object Model:ページ上の要素)を直接操作したいときなどは、参照が変わると困る場合があります。

 そんなときに使うのがuseRefというHookです。

useRef

const Example = () => {
  const ref = useRef(0);

  const handler = () => {
    ref.current = ref.current + 1;
  }

  return (
    <div>{ref.current}</div>
  );
}

 useRefは引数に渡された初期値をcurrentプロパティにもつオブジェクトを返します。handlerを実行すると、その値を+1することができますが、この場合再レンダリングはされません。

useRefによるDOMの操作

const Example = () => {
  const ref = useRef(null);

  const handler = () => {
    ref.current.focus();
  }

  return (
    <input ref={ref} />
  );
}

 このようにするとrefのcurrentプロパティにinput要素が格納されます。handlerを実行すると、input要素のfocusメソッドを実行します。

 これは独自のコンポーネントに対しても同様で、propsのrefに渡すことで、そのコンポーネントの参照を保持し続けることができます。React19からはこの形でいいんですが、それより前のバージョンでは、コンポーネントを定義する関数全体をforwardRefでラップする必要があります。

子コンポーネントの情報を吸い出す

//子コンポーネントに記述
useImperativeHandle(ref, () => ({
  getValue: () => value,
}));

//親コンポーネントに記述
const value = ref.current.getValue();

 Reactでは、親から子にデータを渡すことはあっても、子から親にデータを渡すことはありません。どうしても子から親にデータを渡したいときは、親で管理しているstateの更新関数を子に渡して、子がそれを実行することができますが、直接子が管理するstateを取得することはできません。

 そこで、子コンポーネントにuseImeperativeHandleというHookを使って、子が管理するstateを渡す関数を定義します。そうすると、親で参照を保持しているref.currentからこのメソッドを呼び出すことができるようになります。ここでは、getValueというメソッドがvalueを返すようにしていますので、親コンポーネントでgetValueを呼び出すと、子が管理するstate(value)を受け取ることができます。

 Reactの考え方としては、コンポーネント間の依存関係が強くなってしまうので、あまりおすすめの方法ではないようですが、大変便利な機能だと思います。

コメント