先日行われたFacebookの開発者向けイベント「F8」で、React Fiberの発表が行われていました。 といっても、React関連の新しいプロダクトが発表されたというわけではなく、Reactが一から書き直されたということのようです。
ReactはなぜFiberで書き直されねばならなかったのか?Fiberが解決しようとした課題は何なのか?
その答えを聞くために、React Fiber現状確認というブログエントリで大変詳細にFiberの事を解説されていた小林徹 (Twitter: @koba04)さんに、実際のところを詳しく伺ってきました。 React Fiberとはなんなのか、そしてReactの将来像を探ってみます。
React Fiberとは?
白石 React Fiberってなんですか?まずは概要を教えてください。
小林 Facebookが先日のF8カンファレンスで発表した、Reactの新しいアーキテクチャですね。 現行のバージョンは15なのですが、次期メジャーバージョンである16以降で採用されるアーキテクチャを、React Fiberと呼んでいます。 バージョン16は、夏にリリースされる予定です。
白石 わざわざ名前を付けるくらい、大きなバージョンアップだということですね。
小林 とはいえ、普通にReactを使っているぶんには、ほとんど意識しなくても構いません。 内部は大幅に書き直されていますが、基本的には後方互換性が保たれていますので。これまでちゃんと最新バージョンに追随しているのであれば、アップグレードに苦労するということはないんじゃないかと思います。
白石 なるほど。じゃあReact Fiberは、内部のアーキテクチャ変更が主で、使っている側からすると大きな変更点はないって感じでしょうか?
バージョン16での主な変更点
小林 その、内部のアーキテクチャ変更がすごく重要なんですよね。ただ、16の変更点にも、目立つものはあるので、それらを先に挙げておきましょうか。
まず廃止される機能からいきましょう。
一つ目はReact.createClass()
。これが廃止される代わりに、ECMAScript 6のclass
構文や関数によるComponent定義を使うことが推奨されます。
他には、 proptypes
が廃止されます。
白石 proptypes
って、コンポーネントのプロパティの型チェックを行うための仕組みでしたよね。
小林 はい。今ではプロパティの型は、TypeScriptやFlowなど、React外の仕組みを使ってチェックすることが推奨されています。
とはいえ、proptypes
の仕組みはそのままprop-types
という外部ライブラリに引き継がれていて、このライブラリを利用すれば、proptypes
を使用した既存のコードも動かすことができます。
また、Facebookが開発しているjscodeshift
(筆者による補足: および派生プロダクトであるreact-codemod
)を使うと、移行の手助けになるでしょう。
白石 追加される機能とかはいかがですか?
小林 render()
の結果を文字列や配列で返せるようになったのは大きいですね。
今までは、単一の要素で返さなくてはならなかったので、無駄に全体をdiv
で囲んだりする必要がありましたが、それが不要になります。
ほかには、フラットバンドルになります。
白石 フラットバンドル?
小林 今まではnpmでモジュールをインストールすると、複数のファイルがディレクトリ構造を保ったままダウンロードされていたんです。 なので、React内部のモジュールを直接インポートすることも可能だった。
しかし16からは、高速化やファイルサイズの削減を目的に1つのファイルにまとめられた状態で配布されるようになるので、そういうことができなくなりました。 これは、Reactの内部APIに直接アクセスしていたようなライブラリが動かなくなるかもしれない、ということです。
白石 なるほど。
小林 ほかには、エラーバウンダリという機能もありますね。子供のコンポーネントで起きたエラーを、親コンポーネントでキャッチして処理できる機能です。
React Fiberの本質に迫る
白石 ではそろそろ、Fiberとは何かについて具体的にお聞かせ願えますか?なぜ、FacebookはわざわざReactを全面的に書き直す必要があったのか。
小林 基本的には、仮想DOMを更新してUIに反映するアルゴリズムが変更されたということです。このアルゴリズムのことを「リコンシリエーション(reconciliation)」と呼びます。
これまでの更新処理は、基本的に仮想DOMのツリーを上から再帰的に比較していくというものでした。 なので、途中で停止することもできませんし、更新処理の間アプリケーションがフリーズしてしまいます。 このアルゴリズムは「stack」と呼ばれています。
白石 そのアルゴリズムが変わるわけですね。
小林 はい、今回から導入されるアルゴリズムが「fiber」と呼ばれているのです。
具体的には、再帰的な処理をやめて、ReactElement
ごとの更新処理を連結リスト(linked list)にして、一つ一つの更新処理を独立したものとして扱えるようにしたのです。
それぞれのジョブが独立しているので途中停止も可能ですし、個々のジョブに優先度を付けることも可能になります。
そしてその更新処理がFiberと呼ばれていて、ReactElement
とほぼ一対一に対応しています。FiberはReactElement
と同様、ほかのFiberと親子や兄弟の関係を構築します。
白石 なるほど、更新処理をFiberとして抽象化して、連結リストとしてつなぐことで、処理を止めたり再開したり…を可能にしたわけですね。 同期的な再帰処理を、停止・再開可能な非同期処理にできるわけだ。でも、非同期処理にすると、DOMも随時更新されるということになるのでしょうか?
小林 いえ、DOMの更新などの副作用は、リコンシリエーションの最後にまとめて行われる(コミットフェーズ)ので、そういうことはありません。 そして、Fiberという単位で処理を分割することにより、Fiberのスケジューリングが可能になったんですね。
Fiberの「スケジューリング」?シングルスレッドなのに?
白石 JavaScriptは基本的にシングルスレッドなのに、スケジューリング、ですか?
小林 requestIdleCallback()
やrequestAnimationFrame()
を使って、優先度付きのスケジューリングを擬似的に行っているんです。
例えば優先度の低いFiberは requestIdleCallback()
を使って、ブラウザの処理がひまになった時に実行されます。
優先度の高いFiberは、 requestAnimationFrame()
を使い、できるだけ早いタイミングで実行されます。
白石 うおお、すごい発想!でも、なんでそんな仕組みが必要とされたんでしょう?
小林 最も重要な理由は、ユーザーインタラクションを阻害しないようにすることです。 リコンシリエーションが同期的な処理だと、処理の間UIがフリーズしてしまいます。 Fiberによって非同期になることで、こうした問題を緩和できます。
さらに、DOMの更新処理にも、遅延が許されるものとそうでないものがあります。 例えば、こちらのデモを見てください。
https://koba04.github.io/react-fiber-resources/examples/
このデモは、画面の下半分で5,000個の要素を更新し続けています。 そこで、画面中央のテキストフィールドにキーを入力してみてください(筆者注: 文章では伝わりにくいので、ぜひお試しください)。
「Sync mode」(同期モード)の場合は、キー入力がかなりつっかかって、まともに入力できるものではありません。
一方「Async mode」(Fiberを使用した非同期モード)の場合は、割とスムーズに入力できます。そして、入力中は画面下半分がほとんど更新されません。 これは、画面下半分の更新処理が、低い優先度でスケジューリングされているからです。
白石 なるほど…Fiberのメリットはよくわかりました。しかし、JavaScriptで優先度付きのスケジューリング…Fiberのコード、とんでもなく難しいものになってそうですね(笑)。
小林 はい、Fiberのコードはもはや、OSカーネルがプロセスのスケジューリングしているようなコードに見えます(笑)。 カーネルのコードとかに詳しい人が読んだら、逆に読みやすいのかもしれません。
React Fiberによる様々なメリット
白石 ほか、Fiberのメリットにはどのようなものがありますか?例えば、パフォーマンス面に与える影響とかはいかがでしょうか。
小林 ここは誤解されやすいところなんですが、Fiberは単純な速度の向上を目指したアップデートではありません。 あくまで、リコンシリエーションが非同期化され、ユーザーインタラクションを阻害しなくなったということが重要です。 スピードではなくレスポンスの向上を目指していると言ってもいいでしょう。
なので、ベンチマーク結果が劇的に向上するようなことはないでしょう。 おまけに、後方互換性も保たれているので、16になったときに「何も変わってないじゃないか」と言われるのは、予想の範疇であるとも言えます(笑)。
白石 なるほど(笑)。
小林 ほかのメリットとしては、サーバサイドレンダリングの改善ということは挙げられると思います。 今はレンダリング結果を文字列で返すAPIしかないのですが、ストリームを返すAPIが16で入るかもしれません。 今までサーバサイドレンダリングは、(主な開発元である)Facebook自体が使ってなかったということもあって、これまであまり質の高いものではありませんでしたが、今後は改善されるのではないでしょうか。
白石 非同期化の影響で、ストリーム化できるようになったのですね。コンポーネントのレンダリングで、Node.jsのランタイム全体がブロックするということがなくなりそう。
小林 ほかには例えば、ダブルバッファリングが可能になります。ダブルバッファリングというのは、次に表示するUIをバックグラウンドで描画しておくことで、ちらつきや遅延なく、素早くUIを切り替えられるようにすることです。
React Fiberでそれをどう行うかというと、HTML要素のhidden
属性を使用して、オフスクリーンでDOMをレンダリングしておくのです。
以前はこういうことを行おうとしても、バックグラウンドで描画する処理もメインスレッド上で同期的に行われるので、ユーザーインタラクションを阻害してしまっていました。
React Fiberでは、オフスクリーン・プライオリティという低い優先度が使用されるので、ブラウザがアイドル状態の時を狙って実行されます。UI処理を阻害することはありません。
白石 それも面白い応用方法ですね。Fiberすごい。
小林 他にはCoroutineComponentというのもあります。 これは私もまだ詳しく理解していないのですが、通常は親から子へとレンダリングしていくのを、親のレンダリングを一旦停止して、子のレンダリングが終わるのを待ってから、親のレンダリングを再開するという感じのようです。 おそらく、要素の高さなど、子をレンダリングした結果を使用して、親をレンダリングするのに利用するのではないかと思われます。
React Fiberがもたらす変化
白石 React Fiberは、Reactのエコシステム全体や、ひいてはWeb開発にどのようなインパクトをもたらすと思いますか?
小林 Reactに関連したプロダクトで言うと、まずReact Nativeには、近々Fiberが取り込まれる予定です。 React NativeはもともとUIスレッドとJSを実行するスレッドが分かれていますが、優先度付きの更新処理が可能になるのはReact Nativeでも嬉しい部分だと思いますね。
白石 F8で同時にオープンソース化が発表されていた、ReactVRとかではいかがでしょう?
小林 VRでは、ちょっとした描画の遅れで利用者がVR酔いしてしまうので、fpsが決定的に重要です。 そうなると、レンダリングの優先度を制御するのはとても重要になるわけで、Fiberはすごく意味があるのではないでしょうか。
ほかには、レンダラーの開発が容易になるというのも見込まれます。
白石 レンダラーっていうのはなんですか?
小林 仮想DOMを、実際のUIとしてレンダリングするモジュールのことをレンダラーと呼ぶのです。 これまでも、WebGLやCanvas、はてはハードウェア用のものなど、様々なレンダラーが開発されてきた(筆者補足: 有志が作ったレンダラーのリスト)のですが、今まではReact内部のAPIを直接利用して作られていました。 Fiberでは、レンダラーを開発するためのAPIが整備されており、独自のレンダラーが作りやすくなるのではと期待しています。
白石 なるほど。今後、Reactの適用範囲が広がるにあたって、React Fiberは非常に意味があるアップデートだと言えそうですね。
小林 はい、ほか、Web技術の話で言うと…これはあくまで可能性の域を出ないんですが、Fiberのスケジューリング部分をWebAssemblyで書くようになるという可能性はあるかな、と思っています。
白石 おお…!それは面白い。そして非常にあり得る話に聞こえます。スケジューリングの部分、どう考えても負荷が高くて、しかもUIと関係なさそうだし…!
小林 そうなんですよ。Fiberって、もはや「JavaScriptでそんなコード書いちゃうの」っていうくらい高度なスケジューリング処理などを行っているので、WebAssemblyやprepackなどを使って、そこを徹底的に最適化していくというのが将来的にあり得るんじゃないかな、と思っています。
白石 Fiberのスケジューリング、WebAssemblyによる高速化…。Web開発もここまで進化するのか、という感じです。今後が楽しみですね! 本日は、React Fiberについて様々な疑問に答えていただき、ありがとうございました!