FowardRefを理解する

FowardRefとは

結論から言えば、FowardRefは親から子のコンポーネントのRef参照時に使用します。
よくあるパターンとして再利用可能なコンポーネントに対してRefを付与したい場合などに使います。

そもそもこのFowardRefとは何なのか?
Fowardは「転送」という意味になっており、Fowarding、進行形で「転送する」という意味になります。
Reactにおいては、コンポーネントを通じてその子コンポーネントのひとつにrefを渡すテクニックになります。通常どおり、propsとしてrefを付与すれば参照できると思われますが、Reactの仕様上、親から子のfunctionコンポーネントをprops形式で渡してrefで参照することができないようになっています。そのため参照したい場合は子コンポーネント内でfowardRefを使用する必要があります。

実際、子コンポーネント内でfowardRefを使用せずに親から子のfunctionコンポーネントにrefを付与し、参照しようとすると以下のようなエラーがでます。

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

エラーになるコード

interface FowardProps {
    ref: React.Ref<HTMLDivElement>;
}
const Forward: React.SFC<FowardProps> = (props: FowardProps) => (
  <div ref={ref} style={{ width: "100%" }} />
);

function ForwardRef() {
  const divRef = React.useRef<HTMLDivElement>(null);

  React.useEffect(() => {
    if (divRef.current) {
      console.log(`forwardRefRef div width: ${divRef.current.clientWidth}`);
    }
  });

  return <Forward ref={divRef} />;
}

FowardRefの使用例

では実際にfowardRefを使用した例を見てみましょう。

子コンポーネント

const Forward = React.forwardRef((props, ref: React.Ref<HTMLDivElement>) => (
  <div ref={ref} style={{ width: "100%" }} />
));
Tips

Component definition is missing display nameエラー
Stateless Functional Component には関数名に名前を付ける必要があります。
これはESLintのエラーですべてのコンポーネントに名前が無いとデバッグが難しくなるため導入されました。回避する方法として関数名をつけて返せば良いです、よってアロー関数で返すのは不可。
functionで書き換えるか以下のようにdisplayNameに名前を入れてあげれば回避できます。

Forward.displayName = "Forward";

親コンポーネント

function ForwardRef() {
  const divRef = React.useRef<HTMLDivElement>(null);

  React.useEffect(() => {
    if (divRef.current) {
      console.log(`forwardRefRef div width: ${divRef.current.clientWidth}`);
    }
  });

  return <Forward ref={divRef} />;
}

上記のようにすればdivのDOMにアクセスして情報を取得できるようになります。情報の取得は通常通りref.currentの中に入ってきます。

他にもこんな感じでも書けますね。

import React from 'react';

const Hoge = ({ style }, ref) => {
  return (<div ref={ref} style={style} />);
}

export default React.forwardRef(Hoge);

False positive display-name when using forwardRef

複数のrefを扱いたい時の落とし穴

子コンポーネントで複数のrefが存在するケースがある。この場合も同様でforwardRefで参照が可能ですが…

// Parent
ref={{
    ref1: this.ref1,
    ref2: this.ref2
}}
// Child
export default React.forwardRef((props, ref) => {
  const { ref1, ref2 } = ref;

  return (
    <Child1
      {...props}
      ref={ref1}
    />
    <Child2
      {...props}
      ref={ref2}
    />
  );
});

引用元
https://stackoverflow.com/questions/53561913/react-forwarding-multiple-refs

ただし、これはあくまでJSのコードです。
TypeScriptでやると型が違うと怒られます。正直この辺に関してはまだ調べていかなければいけなさそう。自分自身、親から子に対して複数のrefを渡すというのは、どう実装すれば良いかは不明です。
というよりも、そもそも親が複数のrefを渡すような設計にしない方がよいと思っています。

個人的な意見としては、ReduxやContext APIなどでrefの情報をストアさせて、グローバルに扱えるようにしておけば、まどろっこしいコンポーネント間での受け渡しをせずに済みます。

まとめ

このFowardRefが何をしているのかというとオプトインの機能の役割をしています。それによってコンポーネントがrefを受け取り、それをさらに下層の子に渡しています。
これで下層の子のrefをフォワーディングできるようになるということ。

参考リンク

関連記事