JavaScript
Facebook
reactjs
VirtualDom

React v0.13 RC - 気になる点を検証

More than 3 years have passed since last update.


はじめに

 react.jsのv0.13 RCが先日(2015.02.24)発表されました。と、思ってたら昨日(2015.03.03)v0.13 RC2が発表されていますね。変更点は文章だけで実際のコードの例が無いので初心者の筆者にはいまいちわからなかったです。ということで「多分こういうことかな?」というコードを現在の安定版(v0.12.2)と比較して結果をまとめたいと思います。グダグダとブログで書いているバージョンは以下にて参照可能です。本記事では簡潔に挙動の違いだけに触れます。仕様理解が不足している点や、誤っている点があるかと思いますので、コメント等でご指摘・ご連絡いただければ幸いです。


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);