blog.koba04.comkoba04's twitter accountkoba04's GitHub account

React.js v15.0 changes

2016/03/09 @koba04

v15.0.0がリリースされました(2016/04/08)


RC2が出たので追記

RC2 ではIE、Edgeでマークアップの構造によって一部のテキストが表示されないバグ(#6246)とSVGに関する変更がrevertされました。


React.js v15.0がリリースされたので変更内容などを整理したいと思います。

% npm install --save react@15.0.0 react-dom@15.0.0

今回の一番大きな変更はバージョン番号かなと思います。

0.14から15.0に。

0.x系だとproduction readyではないと思われることから一気に15.0になりましたが0.がなくなっただけで大きな変化があるわけでもないです。 なぜ1.0ではないのかというと1.0というバージョン番号は特別意味のあるものとして扱われるので、そうではなくてすでにproduction readyでありsemverに従っているということを明確にするために0.を取って15.0になりました。

https://facebook.github.io/react/blog/2016/02/19/new-versioning-scheme.html

ちなみにこれまでもminor version(0.13 -> 0.14)などでは破壊的な変更がありましたがpatch version(0.14.1 -> 0.14.2)などは基本的には破壊的な変更はなかったのでバージョンアップのサイクルなどが変わることはないと思います。 (patch versionの更新でUndocumentedなfeatureで破壊的な変更が入ることはありましたが...)

これまでも破壊的な変更をする際は基本的には前のバージョンでwarningを入れてから更新するので、今回もすでに0.14.7を使っていて特にwarningなどが出力されていないのであれば問題なく15.0にアップデートできると思います。

ちなみにIE8のサポートについてはまだIE8対応のコードは削除されていないので動作するはずですが、今後はIE8のためだけのバグFixなどは行わないというステータスです。

https://facebook.github.io/react/blog/2016/01/12/discontinuing-ie8-support.html

Major changes

document.createElement is in and data-reactid is out

これまではReactで構築した全てのDOMに対してdata-reactidという属性が付与されていましたがそれが付与されなくなりました。すっきりしていいですね。 ReactDOM.renderを行ったルートの要素にはdata-reactrootという属性が付与されます。

またこれまでは多くの場合でdocument.createElementを使うよりも高速であるという理由から初期マウント時にはHTML文字列を生成してinnerHTMLで流し込んでいましたが、ブラウザーの改善などにより必ずしもそうとも言えなくなってきたのでdocument.createElementを使って作成するように変更されました。

data-reactidがなぜ必要だったのかはReactのイベントの仕組みに関係しています。 ReactではイベントハンドリングはそれぞれのReactElementと対応付けられたDOM要素にイベントリスナーを登録してハンドリングするのではなく、ReactDOM.renderで指定したルートのDOM要素だけにイベントリスナーを登録してそこで全てのイベントをハンドリングしています。 ルートで受け取ったイベントがどのReactElementと対応付けられたDOMで発生したのかを判定するためにdata-reactidが使用されていました。

イベントは頻繁に発生するため内部ではキャッシュなどを駆使して高速化が図られていたのですがそれによるバグも多く、今回HTML文字列ではなくDOM要素を作成するようになり、DOM要素を保持しておけばいいのでdata-reactidを使ったマッピング情報を保持しておく必要がなくなりました。

ちなみにReactDOM.renderToStringを使って生成したHTML文字列には変わらずdata-reactidが付与されています。

No more extra <span>s

{name}などのように変数を文字列として埋め込んだ際にこれまでは差分更新のためにspanタグで囲まれていたのがcomment nodeに変更されました。

  • v0.14
<div id="app">
<div data-reactid=".0">
<span data-reactid=".0.0">Hello </span>
<span data-reactid=".0.1">React</span>
</div>
</div>
  • v15
<div id="app">
<div data-reactroot="">
<!-- react-text: 2 -->Hello <!-- /react-text -->
<!-- react-text: 3 -->React<!-- /react-text -->
</div>
</div>

この変更はマークアップ構造の変更を生むので更新する際には注意が必要です。 特にCSS周りやテストでspanが挿入されることに依存したコードを書いていると壊れます。

元々が意図しないマークアップが挿入されていたのでそれがなくなってよかったんじゃないでしょうか。

Rendering null now uses comment nodes

renderメソッドでnullを返した場合に、これまでは<noscript>タグがrenderされていましたがcomment nodeに変更されました。 この変更もマークアップ構造の変更を生むので特に:nth-childなどのセレクターを使っている場合には注意が必要です。

また下記のエントリーにもあるように無駄に<noscript>タグが更新されていたような場合に対するパフォーマンスの改善となります。

http://benchling.engineering/deep-dive-react-perf-debugging/

Improved SVG support

[更新]RC2でrevertされました

~~全てのSVGタグがサポートされるようになりました。一般的ではないタグはReact.DOMのヘルパーとしては提供されていませんが、React.createElementで全てのSVGタグを作成できます。~~ ~~全てのSVGタグはキャメルケースやハイフンなどそのままの指定で作成できます。~~ ~~gradientTransformgradientTransformのままでclip-pathclip-pathのまま指定します。~~

~~ちなみにクラスを指定する場合はclassNameではなくて、classで指定します。custom elementsと同じです。~~ ~~それに関するissueはこちら。~~

~~ https://github.com/facebook/react/issues/6211 ~~

このSVGに対する挙動の変更はcustom elementsの挙動と同様であったのですが、HTMLElementとの整合性(class -> classNameなど)がなくなってしまうことが問題だということでrevertされました。 なのでサポートされていないsvgの要素や属性があれば引き続きPRして反映する必要があります。こんな感じで。(#6267)

将来的にはSVGもHTMLも普段SVGやHTMLと同じようにclassはclassのままで指定できるようになるのではないかなと思います。具体的な予定は示されていませんが...。

Breaking changes

v15で最も大きなBreaking changeは上に書いたspanタグを使わなくなったことによるマークアップ構造の変化です。

その他では、v0.14でwaringsを出力していたDeprecatedなAPIが完全に削除されました。

  • ReactのパッケージからfindDOMNode, render, renderToString, renderToStaticMarkup, unmountComponentAtNodeが削除されました。代わりにReactDOMのパッケージにある同名のAPIを使います。

  • Addonとして提供されていたbatchedUpdatescloneWithPropsが削除されました。

  • setProps, replaceProps, getDOMNodeのAPIが削除されました。

New deprecations, introduced with a warning

LinkedStateMixinvalueLinkはほとんど使われておらず、v16で廃止するためのwarningを追加されます。

https://facebook.github.io/react/docs/two-way-binding-helpers.html

必要な場合は下記のパッケージを利用してください。

https://www.npmjs.com/package/react-linked-input

New helpful warnings

  • developmentビルド(NODE_ENVproductionでない)にも関わらずminifiedされている場合はproductionビルドを使うようにwarningが出ます。
    • Reactの中のコードを見たことがある人であればproductionビルドにする必要性がわかるはず...。developmentビルドには大量のデバッグやwarning用のコードが含まれています。
  • styleのwidthやmarginに数値を渡すと自動で単位(px)を付与してくれますが、その際に数値を文字列として渡しているとwarningが出ます。将来のバージョンでは文字列の場合は単位が自動で付与されなくなります。
    • <div style={ {width: 10} }>はOK、<div style={ {width: "10"} }>はwarningが出ます。
  • SyntheticEventに追加でpropertyをセットしようとしたり、すでに解放されているのにアクセスしようとするとwarningが出力されます。
    • 追加でpropertyをセットした場合にwarningが出るのはES2015のProxiesがサポートされている環境のみです。ちなみに自分が実装しました。
  • ReactElementのrefkeyのPropにアクセスしようとするとwarningが出力されます。これらはReact自体が使うためのPropでkeyで使った値が必要な場合は別途Propとして設定する必要があります。
  • DOM ElementのPropに対して、onClickonclickのように大文字・小文字の指定が間違っている場合にはwarningが出力されるようになりました。

Notable bug fixes

  • 幾つかのメモリリークが修正されています。

SyntheticEventでのメモリリークについては自分が修正したので紹介しておくとSyntheticEventのtarget属性が正しく解放されるようになりました。

SyntheticEventはPoolingされており、イベントハンドラーの処理が終了すると初期化されPoolに戻るのですが一番よく使うtarget属性だけ解放されていませんでした。 したがって、下記のようなコードはv0.14では動作していましたがv15では動作しません。

const Component = () => (
    <div onClick={e => {
      setTimeout(() => console.log(e.target)); // <div>click</div>
    }}>
      click
    </div>
);

これはonClickが実行された時点でSyntheticEvent(e)が初期化されており、setTimeoutのコールバック実行時にはすでにtargetの値が初期化(null)されているためです。 上記の場合、targetをローカル変数として保持するかe.persist()を使って保持する必要があります。

SyntheticEventがPoolingされているのは大量のイベントオブジェクトを作成することで発生するGCを避けるなどパフォーマンス上の理由からなのですが、モダンなブラウザーではもう必要ないのではないかということでPoolingをやめることが検討されています。

SyntheticEvent周りは自分が追加したProxyを使ったコードなどかなり混沌としてきているのでPoolingやめてリファクタリングするのはとてもいいと思います。やりたい...。

https://github.com/facebook/react/issues/6190


  • IE10, 11などでのイベントの扱いが改善されています。

  • citeprofileの属性がサポートされました。

  • onAnimationStart, onAnimationEnd, onAnimationIteration, onTransitionEnd, onInvalidのイベントがサポートされました。またobject要素にonLoadイベントが追加されました。

  • shallowCompareなどのいくつかの場所でObject.isによる比較が行われるようになりました(実際にはpolyfill)。これにより、+0 !== -0となったりNaN === NaNとなります。

  • ReactDOMがデフォルトではpropertyとしてではなくattributeとして扱うようになります。これによるEdge caseなバグが修正されました。また属性値がnullの場合に属性が完全に削除されるようになりました。これによりブラウザーがデフォルト値を設定しないようになります。

  • あとblogにはありませんでしたが、Stateless Componentsがnullを返せるようになったのは地味に嬉しいですね!

Regression?