Refsの詳細
renderメソッドからUIの構造が返された後に、 それ自体へのアクセスと、renderから返されたコンポーネントのインスタンス上のメソッドを実行する術が無い事に気が付くかもしれません。
多くの場合、このような事はアプリケーションを通したデータフローの作成で必要とされません。 何故なら、リアクティブ(Reactive)なデータフローは、 最新のpropsがrender()から出力された各子(要素)に送信されていることを常に保証するからです。 ただし、こういった事が必須または有用となるケースが稀に存在します。
<input />
要素(インスタンスの階層下に存在するものとする)の値が空文字列(''
)で更新された後に、
その要素にフォーカスする事を指示するケースを例に考えてみましょう。
var App = React.createClass({
getInitialState: function() {
return {userInput: ''};
},
handleChange: function(e) {
this.setState({userInput: e.target.value});
},
clearAndFocusInput: function() {
this.setState({userInput: ''}); // inputをクリア
// ここで<input />をフォーカスしたい!
},
render: function() {
return (
<div>
<div onClick={this.clearAndFocusInput}>
クリックすると、フォーカスして値をリセット
</div>
<input
value={this.state.userInput}
onChange={this.handleChange}
/>
</div>
);
}
});
この例で、どのようにしてinputに何らかの"指示"をするのか、ということに注目して下さい。 「何らかの」とは、そのプロパティからは導き出すことが出来ない処理を指します。
このケースでは、すぐにフォーカスされるべきであると"指示"したいのですが、
これには幾つかの難題があります。
render()
から返されるものは、"子"コンポーネントの実際の構成では無く、
その瞬間(あなたが、スナップショットを取っていれば(?) )の特定のインスタンスの子の構成に過ぎません。(翻訳に自信なし)
注意:
render()
から返されるものは、実際に描画された子のインスタンスでは無いことを覚えておいて下さい。
render()
から返されるものは、特定の瞬間のコンポーネントの階層下の子インスタンスの構成に過ぎません。
// 反例: このような事をしないで下さい!
render: function() {
var myInput = <input />; // 後で、this上でメソッドの呼び出しをするつもり
this.rememberThisInput = myInput; // 未来のある時点でのinputが取れるはず!
return (
<div>
<div>...</div>
{myInput}
</div>
);
}
この反例では、<input />
は単なる<input />
の構成に過ぎません。
この書き方は、<input />
のための、
本物のbacking instance(援助インスタンス?)を作成するのに使用されます。(翻訳に自信なし)
ref文字列属性
Reactは、render()
から出力された任意のコンポーネントに割り当てる事が出来る非常に特別なプロパティをサポートします。
この特別なプロパティは、render()
から返される何らかのbacking instance相当への参照を可能にしてくれます。
これは、ある時点での本物のインスタンスであることが常に保証されます。
使い方は至ってシンプルです。
-
このように、renderから返されるものに
ref
属性を割り当てます。<input ref="myInput" />
-
別のコード(一般的にはイベントハンドラのコード)で、
this.refs
を通してbacking instanceにアクセスします。this.refs.myInput
refコールバック属性
ref
属性は名前の代わりにコールバック関数にすることが出来ます。
このコールバックは、コンポーネントがマウントされた直後に実行されます。
この描画されたコンポーネントは、引数として渡され、
コールバック関数はそのコンポーネントをすぐに使用することも、
後で使用するために参照を保存しておくことも可能です。
これは、次のようにrenderから返されるものにref
属性を割り当てるのと同じぐらい簡単です。
<input ref={ function(component){ React.findDOMNode(component).focus();} } />
例を完成させる
var App = React.createClass({
getInitialState: function() {
return {userInput: ''};
},
handleChange: function(e) {
this.setState({userInput: e.target.value});
},
clearAndFocusInput: function() {
// inputをクリア
this.setState({userInput: ''}, function() {
// このコードは、コンポーネントの再描画後に実行されます。
React.findDOMNode(this.refs.theInput).focus(); // フォーカスされました!
});
},
render: function() {
return (
<div>
<div onClick={this.clearAndFocusInput}>
クリックすると、フォーカスして値をリセット
</div>
<input
ref="theInput"
value={this.state.userInput}
onChange={this.handleChange}
/>
</div>
);
}
});
この例では、render関数は<input />
インスタンスの記述を返しますが、
this.refs.theInput
を通して本物のインスタンスがアクセスされます。
ref="theInput"
付きの子コンポーネントがrenderから返される限り、
this.refs.theInput
は正当なインスタンスにアクセスします。
これは、<Typeahead ref="myTypeahead" />
のような、
より高階層(非DOM)のコンポーネント上であっても動作します。
概要
特定の子インスタンスに対してメッセージを送る際に、 リアクティブなpropsとstateの流れを介して実行するにはあまりにも不便なケースで、 refsは非常に有用な方法になります。
ただし、これはデータフローに対するgo-toを抽象化したようなものにするべきではありません。 通常はリアクティブなデータフローを使用し、 本質がリアクティブでは無いケースでrefsの保存を使用して下さい。
利点:
-
コンポーネントのクラス上でpublicメソッド(Typeahead上のresetメソッドのような)を定義し、
ref
を通してそれらのpublicメソッドを呼び出すことが可能です(this.refs.myTypeahead.reset()
のようにして)。 -
多くの場合、DOMのサイズを計測するには、
<input />
のような"ネイティブ"なコンポーネントに直接アクセスすることが必須となり、React.findDOMNode(this.refs.myInput)
を介して基盤となるDOMノードにアクセスします。 refsはこれを確実に行う唯一の方法になります。 -
refsは、自動で値の保持(book-kept)を行ってくれます。(翻訳に自信なし) もしその子(要素)が削除されると、そのrefsも削除されるので、 ここでメモリについて心配する必要はありません。(自分自身の参照を保持するような、おかしな事をしない限り)
注意:
-
決して、コンポーネントのrenderメソッドの内部から、refsにアクセスしないで下さい。 コールスタックの何処であれ、コンポーネントのrenderメソッドは実行中となります。(翻訳に自信なし)
-
もし、GoogleのClosure Compilerによってコードが破壊されないようにしたい場合は(翻訳に自信なし)、 文字列として指定されたものに、プロパティとしてアクセスしないことを保証して下さい。 これは、もしrefが
ref="myRefString"
として定義されたのであれば、this.refs['myRefString']
を使用しなければいけない、という事です。 -
Reactを使用したプログラムの開発経験が乏しいと、 アプリケーションで"何かを実現する"のに、すぐにrefsを使いたくなるかもしれません。
このような場合は、stateがコンポーネント階層の何処で所有されるべきなのかを、もう少し注意深く考えてみて下さい。 その結果、より高い階層でstateが"所有"されるべきであると判明することがよくあります。
stateをその場所に配置することで、"何かを実現する"ためにresを使用したいという欲求を退け、 代わりにデータフローがあなたの目的を達成することになるでしょう。