はじめに
react.jsのv0.13 RCが先日(2015.02.24)発表されました。と、思ってたら昨日(2015.03.03)v0.13 RC2が発表されていますね。変更点は文章だけで実際のコードの例が無いので初心者の筆者にはいまいちわからなかったです。ということで「多分こういうことかな?」というコードを現在の安定版(v0.12.2)と比較して結果をまとめたいと思います。グダグダとブログで書いているバージョンは以下にて参照可能です。本記事では簡潔に挙動の違いだけに触れます。仕様理解が不足している点や、誤っている点があるかと思いますので、コメント等でご指摘・ご連絡いただければ幸いです。
- React v0.13 RC での変更点 – propsの警告
- react.js v0.13 RC – staticsの中の’this’に注意
- react.js v0.13 RC – unmount済みComponentでsetStateまたはforceUpdate
- react.js v0.13 RC – setStateで関数を渡せるようになった
propsをMutateした場合の警告追加
react.jsでコンポーネントのprops
を変更した場合にWarning
が表示されるようになりました。以下のようなコード(を書くことはまずありえませんが...)があったとします。
var ExampleApplication = React.createClass({
render: function() {
// ここでthis.props.nameを無理矢理変更
// 出力結果は「mutated name!」になります
return <p>{this.props.name = 'mutated name!'}</p>;
}
});
var start = new Date().getTime();
React.render(
<ExampleApplication name='kenfdev' />,
document.getElementById('container')
);
v0.13rcより前は特に何もありませんでしたが、v0.13rcからはdevelopment mode時のみではありますが、以下の警告が表示されます。
Warning: Don't set .props.name of the React component . Instead, specify the correct value when initially creating the element.
staticsの'this'
コンポーネントで定義できるstatics
がv0.13rcからコンポーネントに自動でバインドされなくなります。
var MyComponent = React.createClass({
// static method
statics: {
message: 'Hello',
staticMethod: function() {
// < v0.13rc:thisはコンポーネント(MyComponent)に自動バインド
// v0.13rc:thisはコンポーネントに自動バインドされない
return this.message;
}
},
render: function() {
}
});
// messageを持つオブジェクトを準備
var caller = {
message: 'Hey!'
};
// Greetings component
var Greetings = React.createClass({
render: function () {
// < v0.13rc:staticMethodはMyCompoentにバインドされているので出力結果は「Hello」
// v0.13rc:staticMethodはMyComponentにバインドされておらず、呼び出し元をcallerに明示的に設定している為出力結果は「Hey!」
return <div>{MyComponent.staticMethod.call(caller)}</div>
}
});
React.render(<Greetings></Greetings>, document.body);
アンマウントされたコンポーネント内でのsetState、forceUpdate
react.js v0.13rcからアンマウント済みのコンポーネントでsetState、forceUpdateが呼ばれたとしても、例外throw
するのではなくWarning
で留まる様になった。PromiseによってResolveされるのが大きく遅延した場合などにコンポーネントが先にアンマウントされてしまい、結果的に後になって例外が発生してしまう現象をどうにかしてほしい、という要望に答えてくれたみたいです。以下、setState
をアンマウント後のコンポーネントで呼び出した例。
// Helloコンポーネントを2秒後にアンマウントさせる為の設定
var timeoutBeforeUnmount = 2000;
// The Hello Component
var Hello = React.createClass({
getInitialState: function() {
return {message: 'World'};
},
componentDidMount: function() {
var self = this;
setTimeout(
function() {
// setStateを呼び出す
self.setState({message: 'again to the World'})
},
// Helloコンポーネントの1秒後にsetStateを発火させる
timeoutBeforeUnmount + 1000
);
},
render: function() {
return <div>Hello {this.state.message}</div>;
}
});
React.render(<Hello />, document.getElementById('container'));
// Helloコンポーネントを2秒後にアンマウント
setTimeout(function() {React.unmountComponentAtNode(document.getElementById('container'))}, timeoutBeforeUnmount);
以下、結果の比較
# react.js < v0.13rc 例外発生
Uncaught Error: Invariant Violation: replaceState(...): Can only update a mounted or mounting component.
# v0.13rc <= react.js 警告止まり
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
setStateに関数
react.js v0.13rcからsetStateに関数を渡せるようになりました。何がうれしいのかというと、これは筆者の浅はかな知識で書きますが、同じ関数内でstate
に変更を加える場合、state
が即時反映されないが為に意図した結果にならないシチュエーションがあります。
var Hello = React.createClass({
getInitialState: function(){
return {count:0};
},
handleClick: function(e) {
// countをインクリメント
this.setState({count: this.state.count + 1});
// 同一関数内で再度インクリメント
// 2になってくれるかと思いきや、this.state.countは即時反映されるわけではないので、上のsetStateも下のsetStateも「0 + 1」を実行してしまい、結果は「1」になってしまう。
this.setState({count: this.state.count + 1});
},
render: function() {
return (<div onClick={this.handleClick}>
Click Me and the Count will try to increment by 2 but will fail!<br />
Count: {this.state.count}</div>);
}
});
React.render(<Hello />, document.body);
この対策としては_pendingState
を使う手法がある様です。handleClick
の中身が以下の場合は出力結果は2ずつインクリメントされます。
this.setState({count: this.state.count + 1});
// 'this._pendingState'なら即時反映された値にアクセスできる
this.setState({count: this._pendingState.count + 1});
こんなことをしなくて済むようにv0.13rcからはsetState
に関数を渡して最新状態のstate
にアクセスできるようになっています。また、高度なstate
の変更もこれでできるようになりそうですね。
var Hello = React.createClass({
getInitialState: function(){
return {count:0};
},
handleClick: function(e) {
// setStateに関数を渡すことが可能。この計算は「0 + 1」となる
this.setState(function(state,props) {return {count: state.count + 1};});
// 最新状態のstateにアクセス可能なので、この計算は「1 + 1」となる
this.setState(function(state,props) {return {count: state.count + 1};});
},
render: function() {
return <div onClick={this.handleClick}>Count: {this.state.count}</div>;
}
});
React.render(<Hello />, document.body);