ブログBlog

    • React – 3つのref

    • 2018年7月25日
    • reactjs
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を発火させるパターン。親側で実際の表示内容を定義する。
class Parent extends React.Component{
renderRow = (hoge) => {
return <div>{hoge}</div>;
}
render(){
return <DataGrid renderRow={this.renderRow} />;
}
}
class DataGrid extends React.Component{
anyLogic = () => {
// any logic here
this.setState({ hoge: xxxxxx });
}
render(){
return this.props.renderRow(this.state.hoge);
}
}
3番目。Composableでない。様々なコンポーネントを組み合わせてアプリケーションを実装していく際に、String Refsは障害になってしまう。例えば、とあるライブラリがあったとして、ライブラリ内の要素にString Refsをつけていると、ライブラリの利用側で別のrefをつけることができなくなってしまう。 ※記事中のissue#8734 -> https://github.com/facebook/react/issues/8734
// 例:ref`hoge`を呼び出し側で直接指定できない。
class Parent extends React.Component{
someLogic = () => {
this.refs.hoge.refs.hoge.focus();
// ↑こんな感じでHogeコンポーネントで定義しているrefを利用側で書かないといけなくなる
// もしHogeコンポーネントを仕様変更した場合、呼び出し側も修正しないといけなくなる可能性が高くなる。
}
render(){
return <Hoge ref="hoge"/> //←Hogeコンポーネントで指定している`hoge`ではない
}
}
class Hoge extends React.Component{
render(){
return <div ref="hoge"></div>
}
}
その点、Callback Refsであれば柔軟に対応可能。
// 例:Hogeのrefを呼び出し側でも参照できるようにする
class Parent extends React.Component{
constructor(props){
super(props);
this.hogeRef = null;
}
someLogic = () => {
this.hogeRef.focus();
// ↑こんな感じでHogeコンポーネントのrefを利用できる
// もしhogeコンポーネントを仕様変更しても影響範囲を狭めることができる。
}
render(){
return <Hoge targetRef={(e) => {this.hogeRef = e;}/>;
}
}
class Hoge extends React.Component{
constructor(props){
super(props);
this.hogeRef = null;
}
targetRef = (e) => {
this.hogeRef = e;
this.props.targetRef(e); // propsのcallbackを使って利用側でもrefが参照できるようにする
}
anyLogic = () => {
this.hogeRef.focus(); // もちろん自分自身もrefを利用可能
}
render(){
return <div ref={this.targetRef}></div>;
}
}

Creating Refs

Callback RefsComposableなコンポーネント実装を行う際に柔軟に対応できるとのことでした。 一方、16.3でリリースされたCreating RefsComposableを維持しつつも、よりシンプルに実装できます。 Callback Refsはfunctionとして定義する必要があるのに対し、Creating Refsは以下のようにrefを定義できます。
// Callback Refs
class CallBackRefs extends React.Component{
constructor(props){
super(props);
this.inputRef = null;
}
anyLogic = () => {
this.inputRef.focus();
}
render(){
return <input ref={ e => { this.inputRef = e; } } /> // functionとして定義する必要がある
}
}
// Creating Refs
class CreatingRefs extends React.Component{
constructor(props){
super(props);
this.inputRef = React.createRef(); // ①インスタンスを作って、、
}
anyLogic = () => {
// `current`で要素にアクセスする
this.inputRef.current.focus();
}
render(){
return <input ref={this.inputRef} /> // ②代入するだけ
}
}

まとめ

コンポーネント指向に則り再利用性を考慮しながら柔軟にコンポーネントを組み合わせてアプリを迅速に安全に開発するために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
class ForwardRefs extends Component {
constructor(props){
super(props);
this.childRef = React.createRef();
}
handleClick = () => {
alert(this.childRef.current.value);
}
render(){
return (
<div>
<Target ref={this.childRef}/> // refを直接指定
<button onClick={this.handleClick}>input value</button>
</div>
);
}
}
const Target = React.forwardRef((props, ref) => {
// `forwardRef`としてコンポーネント定義すると、引数としてrefが渡ってくる
// ただし「`stateless component`でのみ使用可能」、という制約がある
return (
<div>
<p>Target</p>
<input ref={ref}/>
</div>
);
});

この記事を書いた人 : 國田健史

一覧へ戻る

開発についてのお問い合わせはこちら

お問い合わせ

JOIN OUR TEAM

積極採用しています。私たちと一緒に働きませんか?