React.js v0.13のRC2がリリースされたのでまとめてみます。
- http://facebook.github.io/react/blog/2015/02/24/react-v0.13-rc1.html
- http://facebook.github.io/react/blog/2015/03/03/react-v0.13-rc2.html
- http://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html
今回のバージョンで何か大きく変更があるというよりもv0.14でやりたいことに向けての布石が多いように感じます。
試すときはこの辺りから。
1 2 | |
1 2 3 4 5 | |
Propを変更するとwarninngが出ます (Breaking Change)
development環境でPropをelement作成後に変更することはdeprecatedになってwarningが出るようになりました。 つまりimmutableなものとして扱う必要があります。
1 2 3 4 5 | |
これまでの問題点
- Propを直接変更してしまうと元の値を破棄してしまうのでdiffがなくなってしまいます。この場合、
shouldComponentUpdateを実装している場合に比較時に差分を検出出来なくてDOM構造に差分があるはずなのに実際には反映されない可能性がありました。 - またPropが変更されることがあるためcreateElementの時点でPropTypesのValidationも出来ず、それによってエラー時のstacktraceが深くなったりFlowによる静的解析にとっても都合がよくなかったりという面もありました。
それに対しての提案
- 動的にしたい場合は↓のような形で書くことでも可能です。
1 2 3 4 5 6 7 8 9 10 11 12 | |
- 現時点ではネストしたオブジェクトについては変更してもwarningは出ません。基本的にはimmutable.jsなどを使って完全にimmutableに扱った方がいいですが、mutableなオブジェクトは多くの場面で便利だし今回はネストしたオブジェクトはwarningの対象外となりました。
1
| |
- PropTypesのwarningをReactElementの作成時に行うなうようになりました。Propを変更するために↓のようにcloneしてReactElementにPropに値を追加するのは正しい方法です。
1 2 | |
statics内のメソッドに対してautobindingされなくなりました (Breaking Change)
staticsに定義したメソッドをonClickなどにバインドした時にcomponentをバインドしなくなりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
refを設定する処理の順番が変わりました (Breaking Change)
refに指定されたcomponentのcomponentDidMountが呼ばれた後になります。
これは親componentのcallbackをcomponentDidMountの中で読んでいる場合だけ気にする必要があります。そもそれもこれはアンチパターンなので避けるべきですが…。
componentDidMountは子componentから順番に呼ばれるので下記のrefDivはChildのcomponentDidMountの時点では設定されていません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
this.setState()が第1引数に関数を受け取れるようになりました
1
| |
のようにすることでthis._pendingStateを使うことなくトランザクションが必要とされるstateの更新を行うことが出来ます。
1 2 3 4 5 6 7 8 9 | |
setStateの呼び出しが常に非同期になります (Breaking Change)
ライフサイクルメソッドの中でのsetStateの呼び出しが常に非同期でバッチとして処理されます。以前は最初のマウント時の呼び出しは同期的に行われていました。
1 2 3 4 5 6 | |
setStateとforceUpdateをunmountされたcomponentに対して呼んだ時に、エラーではなくwarningが出るようになりました (Breaking Change)
非同期処理の結果をsetStateして反映させるときに、isMountedでブロックしなくてもよくなったのはいいですね。
privateなプロパティが整理されました (Breaking Change)
this._pendingStateやthis._rootNodeIDなどのprivateなプロパティが削除されました。
ES6 classesによるReactComponentの作成がサポートされました
これについては↓に書きましたが、ES6 classesによって作成されたcomponentにはcreateClassにはあるgetDOMNode、setProps、replaceStateが含まれていなかったりmixinが指定出来ないなど注意点がいくつかあります。
React.findDOMNode(component)のAPIが追加されました
これは既存のcomponent.getDOMNode()を置き換えるAPIです。
getDOMNode()はES6 classesによって作成されたcomponentでは提供されていません。
refがcallbackスタイルで指定できるようになりました。
1
| |
この変更はこの後で書くownerの扱いの変更に関係しています。
childrenにiteratorやimmutable-jsのsequenceを指定出来るようになりました
immutable-jsを使っている人にとってはいいですね。
ComponentClass.typeはdeprecatedになりました
代わりにComponentClassをそのまま使ってください。
ownerベースのcontextを使っていてparentベースのcontextと一致しない場合にwarningが出るようになります
そもそもowner? parent?という感じかと思うので簡単に説明します。
owner and parent
Reactは”parent”と”owner”を持っています。”owner”はReactElementを作ったcomponentです。
1 2 3 4 5 | |
この場合、spanのownerはFooでparentはdivになります。
context
これはdocument化されてないfeatureですが、”owner”から子や孫に渡すことが出来る”context”というものがあります。
簡単にコードを書くとこんな感じです。見てもらえればどんなfeatureなのかわかるかと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
react-routerではparentベースのcontextに依存していたので対応が大変そうでした。
問題点
- ownerは密かにReactElementに追加されているので気づかないうちに挙動が変わることが発生します。↓の場合はそれぞれのinputのownerが異なりますし、
React.addons.cloneWithPropsを使った場合もownerが変わります。
1 2 3 4 5 6 | |
- ownerは実行時のstackによって決定します。↓の場合、
spanのonwerは実際はBでAではありません。これはcallbackが実行されたタイミングに依存するからです。
1 2 3 4 5 6 7 8 9 10 | |
- また、JSXが書いているscope内にReactが必要なのは、Reactが現在のownerを保持していてJSXの変換がそれに依存しているからという意外なところに影響があったりもします。
それに対する提案
- ownerベースのcontextの代わりにparentベースのcontextの導入を考えているのでそれを使うことです。ほとんどのケースはparentベースのcontextでも問題ないです。
- ownerベースのcontextが必要になる場合はほとんどないはずだしコードを見直すべきです。
未解決
refはまだownerベースのままで、これについてはまだ完全に解決出来ていません。- v0.13ではcallbackでもrefが定義出来るようなりましたがこれまでの宣言的な定義方法も残されています。宣言的な定義方法に代わる何かいい方法がない限りこのAPIは廃止されません。
{key: element}(Keyed Object)の形式でchildに渡すとwarningが出るようになりました
v0.12では{key: element}の形式でkeyが指定したらelementを渡すことが出来ましたが、これはあまり使われてないし問題となる場合があるので使うべきではないのでwarningが出るようになりました。
1
| |
問題点
- 列挙される順番はkeyに数値を指定した場合を除いては仕様として定義されてないので実装次第になってしまいます。
- 一般的にobjectをmapとして扱うことは型システムやVMの最適化やコンパイラーにとって好ましくないし、さらにセキュリティ上のリスクもあって↓のような場合にもし
item.title === '__proto__'を指定されたら….
1 2 3 | |
それに対する解決
- ほとんどの場合、
keyを設定したReactElementの配列にすれば問題ないはずです。
1 2 | |
this.props.childrenを使った場合など、keyを指定することが出来ない場合もあるかもしれません。その場合はv0.13で追加されたReact.addons.createFragmentを使うことでKeyed ObjectからReactElementを作成することが出来ます。- 注意として、これはまだrenderの戻り値として直接渡せるものではないのでなどでラップしてあげる必要があります。
1<div>{React.addons.createFragment({ a: <div />, b: this.props.children })}</div>React.cloneElementが追加されましたこれはこれまで
React.addons.cloneWithPropsと似たAPIです。 異なる点としては、styleやclassNameのmergeが行われなかったりrefが保持される点があります。cloneWithPropsを使ってchildrenを複製した時にrefが保持されなくて問題となるという報告が多くあったのでこのAPIではrefを保持するようになりました。cloneElement時にrefを指定すると上書きされます。1 2 3
var newChildren = React.Children.map(this.props.children, function(child) { return React.cloneElement(child, { foo: true }) });このAPIはv0.13でPropがimmutableなものとして扱われるようになったことで、Propを変更するためにelementをcloneする機会が増えたため必要となりました。
React.addons.cloneWithPropsはそのうちdeprecateになりますが今回のタイミングではなりません。React.addons.classSetがdeprecatedになりました必要な場合はclassnamesなどを使用してください。
jsxコマンドで
--targetoptionとしてECMAScript versionを指定出来るようになりました。 (Breaking Change)es5がデフォルトです。es3はこれまでの挙動ですが追加で予約語を安全に扱うようになりました(egthis.staticはthis['static']にIE8での互換性のために変換されます)。jsxコマンドでES6 syntaxで変換した際にclassメソッドがdefaultではenumerableではなくなりました
Object.definePropertyを使用しているため、IE8などをサポートしたい場合は--target es3optionを渡す必要があります。- Original
1 2 3 4 5 6 7 8 9 10 11 12 13
class Hello extends React.Component { foo() { console.log("foo"); } render() { return <div>hello</div>; } } Hello.static = { bar() { console.log("bar"); } };- ES5
1 2 3 4 5 6 7 8 9 10 11 12 13
var ____Class0=React.Component;for(var ____Class0____Key in ____Class0){if(____Class0.hasOwnProperty(____Class0____Key)){Hello[____Class0____Key]=____Class0[____Class0____Key];}}var ____SuperProtoOf____Class0=____Class0===null?null:____Class0.prototype;Hello.prototype=Object.create(____SuperProtoOf____Class0);Hello.prototype.constructor=Hello;Hello.__superConstructor__=____Class0;function Hello(){"use strict";if(____Class0!==null){____Class0.apply(this,arguments);}} Object.defineProperty(Hello.prototype,"foo",{writable:true,configurable:true,value:function() {"use strict"; console.log("foo"); }}); Object.defineProperty(Hello.prototype,"render",{writable:true,configurable:true,value:function() {"use strict"; return React.createElement("div", null, "hello"); }}); Hello.static = { bar:function() { console.log("bar"); } };- ES3
1 2 3 4 5 6 7 8 9 10 11 12 13
var ____Class0=React.Component;for(var ____Class0____Key in ____Class0){if(____Class0.hasOwnProperty(____Class0____Key)){Hello[____Class0____Key]=____Class0[____Class0____Key];}}var ____SuperProtoOf____Class0=____Class0===null?null:____Class0.prototype;Hello.prototype=Object.create(____SuperProtoOf____Class0);Hello.prototype.constructor=Hello;Hello.__superConstructor__=____Class0;function Hello(){"use strict";if(____Class0!==null){____Class0.apply(this,arguments);}} Hello.prototype.foo=function() {"use strict"; console.log("foo"); }; Hello.prototype.render=function() {"use strict"; return React.createElement("div", null, "hello"); }; Hello["static"] = { bar:function() { console.log("bar"); } };JSXによる変換でharmony optionを有効にすることでspread operatorを使えるようになりました
JSXの中ではこれまでもspread attributesとしてサポートしていましたが、JSのコード内でも使えるようになりました。
1var [a, b, ...other] = [1,2,3,4,5];JSXのparseに変更があります (Breaking Change)
elementの内側に
>or}を使った時に以前は文字列として扱われましたがparseエラーになるようになりました。1 2 3
render() { return <div>} or ></div>; // parse error! }v0.14に向けて
今回の変更を踏まえてReact v0.14では静的な要素においていくつかの最適化が可能になります。 これらの最適化は以前はtemplate-baseなフレームワークでのみ可能でしたが、ReactでもJSXと
React.createElement/Factoryのどちらでも可能になります。詳細は下記のissueにあります。 まだ議論もされてないので変わる可能性は大きいと思いますが。
Reuse Constant Value Types
これは静的なelementに変更できないものとして扱うことでdiffのコストを減らすというものです。
例えばこんな感じにするとか
1 2 3 4 5 6 7 8
function render() { return <div className="foo" />; } ↓ var foo = <div className="foo" />; function render() { return foo; }Tagging ReactElements
これはReactElementにtag付けをしてそれを使ってdiffアルゴリズムを最適化するというもののようです。
Inline ReactElements
これはproductionビルドのときに、React.createElementではなくてinline objectに変換することでReact.createElementのコストを削減するというものです。
こんな感じ
1 2 3 4 5
<div className="foo">{bar}<Baz key="baz" /></div> ↓ { type: 'div', props: { className: 'foo', children: [ bar, { type: Baz, props: { }, key: 'baz', ref: null } ] }, key: null, ref: null }こうするとReact.createElementの時に行っているPropTypesやkeyに対するvalidationが出来ないので、developmentビルドの時には適用しないことを想定しているようです。
というわけで、React v0.13をダラダラと書いてみました。
- 注意として、これはまだrenderの戻り値として直接渡せるものではないので