React – 3つのref

React16.3から、refを操作するための新しいAPIが2つ追加されていました。
・createRef
・forwardRef

ref自体頻繁に使用するものではないと思いますが、
例えばinput要素のfocusを操作したいといった場合にrefを指定することがあります。
今回は上記の2者の中でも利用ケースが多いと想定されるcreateRefを取り上げたいと思います。

以前からのrefとcreateRef

https://reactjs.org/blog/2018/03/29/react-v-16-3.html#createref-api

React16.3より前のバージョンでは、

  • String Refs
    string値でrefを指定する
    <input ref="hoge" />
  • Callback Refs
    callback関数でコンポーネント要素と任意に宣言した変数とを紐付ける
    <input ref={(e) => {this.hoge = e}} />

が利用可能でした。

これらに加えて、React16.3ではさらにcreateRefが加わりました。
Reactのガイドhttps://reactjs.org/docs/refs-and-the-dom.htmlでは、Creating Refsという表記があります。

String Refsはレガシーとされており、いくつか問題があることから、いずれは削除されることが表明されています。
https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs

String Refsにはどんな問題があるか?

https://github.com/facebook/react/pull/8333#issuecomment-271648615

によると、Dan Abramov氏が以下のように述べておられる箇所がありました。

  • It requires that React keeps track of currently rendering component (since it can’t guess this). This makes React a bit slower.
  • It doesn’t work as most people would expect with the “render callback” pattern (e.g. <DataGrid renderRow={this.renderRow} />) because the ref would get placed on DataGrid for the above reason.
  • It is not composable, i.e. if a library puts a ref on the passed child, the user can’t put another ref on it (e.g. #8734). Callback refs are perfectly composable.

1番目の問題としては、String Refsthisの中身を推測することができないがために、表示されているコンポーネントの状態をReact側で保持させておく必要がある。そのため、若干動作が遅くなる、とのことだそうです。

2番目。String Refsはコンポーネントに直接指定される構造のため、”render callback”パターンのようなことはできないとのこと。
https://github.com/facebook/react/pull/8333#issuecomment-277079817
にあるように、DataGridを例にすると、String RefsDataGridに指定されるため、MyComponentではなくDataGridを通してアクセスすることになってしまう。
例えばMyComponent上でrefにアクセスする場合、DataTableにrefを付与した上で、this.refs.dataGrid.refs.hogeなどどする必要がある。
Callback Refsだったら直接MyComponent上で保持し、this.hogeのようにアクセスできる。

参考:”render callback”パターン
子コンポーネントのrender時にpropsで定義したcallbackを発火させるパターン。親側で実際の表示内容を定義する。

3番目。Composableでない。様々なコンポーネントを組み合わせてアプリケーションを実装していく際に、String Refsは障害になってしまう。例えば、とあるライブラリがあったとして、ライブラリ内の要素にString Refsをつけていると、ライブラリの利用側で別のrefをつけることができなくなってしまう。
※記事中のissue#8734 -> https://github.com/facebook/react/issues/8734

その点、Callback Refsであれば柔軟に対応可能。

Creating Refs

Callback RefsComposableなコンポーネント実装を行う際に柔軟に対応できるとのことでした。
一方、16.3でリリースされたCreating RefsComposableを維持しつつも、よりシンプルに実装できます。
Callback Refsはfunctionとして定義する必要があるのに対し、Creating Refsは以下のようにrefを定義できます。

まとめ

コンポーネント指向に則り再利用性を考慮しながら柔軟にコンポーネントを組み合わせてアプリを迅速に安全に開発するためにCallback RefsCreating Refsの利用が推奨されています。
また、Callback Refsよりも手軽に利用できるCreating Refsを使うことでちょっとしたref操作であれば簡単に実現できるようになったのではないでしょうか。

参考:
https://leoasis.github.io/posts/2017/03/27/react-patterns-render-callback/
http://developer.medley.jp/entry/2018/02/27/170000

ーー

続 React16.3

createRefと一緒に16.3で追加されたAPIとしてforwardRefがあります。
発展途上のもののようですが、参考として調べてみました。

forwardRef

https://reactjs.org/blog/2018/03/29/react-v-16-3.html#forwardref-api
様々なコンポーネントを組み合わせることにより、一つのアプリケーションを作成するわけですが、
通常のrefだと子のコンポーネントで直接指定した場合、そのコンポーネントのrefになってしまいます。


<Child ref={this.childRef}/> // Childのrefになってしまう

なので、子のコンポーネントのさらに子の要素にrefをつけたい場合、わざわざ別の名前でpropsを指定する必要が生じます。
これを解決するのがforwardRefです。
https://reactjs.org/docs/forwarding-refs.html