同期APIを使用した反応キャッシュ、タイムスライス、およびフェッチ

さて、今年はReactの年のようです。 16.7-alpha.0に付属している新しいキラー機能-フックを聞いたことがあるでしょう。タイムスライシングやサスペンスなど、他のすばらしい機能についても聞いたことがあるでしょう。

この記事の目的は、新機能の使用方法を説明することではなく、それらがどのように構築されたかを証明することです。私たちが遊んでいるものを理解するためだけに。

また、この機能を発見した方法で書かれています。それはおそらく考えられていた方法ではありませんが、これが私がポイントを得た方法です。

読んでいるとわかること:

  • 非同期JavaScriptとイベントループ
  • Reactの代数効果、例付き
  • 繊維および反応段階

なぜこの投稿を書いたのですか?

この投稿を書きたいと思ったのは、同期APIを使用して非同期操作を使用できるようにする、この特別で実験的な機能でした。

const bulbasaur = ApiResource.read()?…なに?同期?!

react-cacheライブラリは、同期APIで非同期操作を使用する機能を作成します。これは、Reactが内部でどのように機能するかを知りたいと思った機能です。このライブラリに関するダンアブラモフとアンドリュークラークによるプレゼンテーションは次のとおりです。

それはどのように可能ですか?同期呼び出しを使用してリモートデータを取得するにはどうすればよいですか?

この例を深く掘り下げて、react-cacheがそのような機能を実装する方法を理解し、それがどのように機能するかを発見してみましょう。この話は、ファイバーアーキテクチャから始まります。

JavaScript操作の制御

ファイバーアーキテクチャにより、Reactはタスクの実行を制御できます。 Reactが被った複数の問題を解決するために構築されました。ここに私の注目を集めた2つがあります。

  • データ取得よりもユーザー入力のように、特定のイベントよりも優先順位を付ける
  • React計算を非同期的に分割して、メインスレッドの可用性を維持し、長いレンダリングプロセス中にそれをブロックしないようにします

Reactだけでなく、JavaScriptアプリケーション内で状態の変更をトリガーするものはすべて、非同期操作によるものです。これらには、イベントのsetTimeout、fetch、およびlistenersが含まれます。

非同期操作は、複数のJavaScriptコアの概念を通じて管理されます。

  • タスク(マイクロ、マクロ、レンダリングなど)
  • イベントループ
  • コールスタック

これらの概念に慣れていない場合は、Jake Archibaldによるこのビデオをご覧になることをお勧めします。

ファイバーのおかげで、ユーザー入力はフェッチ呼び出しなどの他の非同期操作の前に解決されます。

これはどのように可能ですか?

さて、上のArchibaldの講演は、イベントループがどのように機能するかを学ぶ私自身の道の最初の敷石でした。彼は、たとえばPromise APIを介して生成されたマイクロタスクが実行され、次のマクロタスクの前にフラッシュされると言います。このプロセスは、setTimeoutなどのコールバックベースのメソッドを使用します。

「ユーザー入力とデータの取得」の比較を覚えている場合、onChange解決後にチームはどのように取得解決を行いましたか?

これらの概念はどれも同じ仕様であるWhatWG / HTML5 / Ecma-262に当てはまらず、ブラウザやJSエンジンなどの異なる場所から提供されます。

つまり、setTimeoutの後、Promiseをどのように解決するのでしょうか?

これは私には絶対に狂ったように聞こえましたが、それがどのように機能するのかを知るのは本当に困難でした。実際には、それはより高いレベルで行われます。

後で、React RallyでBrandon Dailからの素晴らしい講演を見ました。これは、Reactファイバーアーキテクチャのおかげで出荷された新しいタイムスライシングおよびサスペンス機能を示しています。

Dailによると、ファイバーは、スタック内の各アイテムがファイバーと呼ばれる通常のJavaScriptコールスタックとまったく同じです。関数(+メタデータ)を表すフレームに依存するコールスタックとは異なります。むしろ、ファイバーはコンポーネント(+メタデータ)を表します。繊維は、それについてすべてを知っているコンポーネントの周りの巨大な箱として見てみましょう。

これら2つの概念には重要な違いがあります。

第一に、コールスタックは、JavaScriptコードを駆動するネイティブ部分の上に構築された機能です。すべてのJavaScript関数呼び出しをスタックし、それらを独自に実行することを目指しています。関数を呼び出すたびに、スタックに追加されます。コールスタックがないと、明確で詳細なエラースタックトレースを取得できません。また、コールスタックはJavaScriptコードから到達できないため、制御することは非常に難しく、さらには不可能です。

一方、ファイバーはファイバーのスタックのように同じ概念を表していますが、JavaScriptコードで構築されています。最も小さなユニットは機能ではなく、コンポーネントです。実際にはJavaScriptユニバースで実行されます。

ファイバーアーキテクチャがJavaScriptで完全に構築されているという事実は、それを使用、アクセス、および変更できることを意味します。標準のJavaScriptを使用して作業できます。

私を間違った方向に導いたのは、ReactがJavaScriptが機能している内部的な方法を遮断するための回避策を使用していると思ったことです。そうではありません。ファイバーは、Reactコンポーネントに関する情報を所有し、ライフサイクルと相互作用できるJavaScriptオブジェクトです。 Reactの内部機能に対してのみ機能します。

アイデアは、コールバックタスクの前にフェッチマイクロタスクの解決を実行するように伝えるなど、JavaScriptの動作方法を再定義することではありません。さまざまなライフサイクルメソッドの呼び出しを中断するなど、特定のコンテキストでReactメソッドを呼び出すべきかどうかについてです。

ねえ、ちょっと待って!あなたは、繊維がReactアプリのすべてを完全に制御できると言っていますか?しかし、コンポーネントはどのようにしてReactに何かをやめるように指示できますか?

代数効果、はい、しかしJavaScriptでお願いします

Reactは、ファイバーアーキテクチャのおかげで、コンポーネントを制御し、コンポーネントが実行されているかどうかを知ることができます。現在不足しているのは、特定のコンポーネントに対して何かが変更されたことをReactに伝える方法であり、この変更を処理します。

これが代数的効果がゲームに入る場所です。

代数効果はJavaScriptに存在するものではありません。それらが何であるかをより高いレベルの説明で説明しようとします。

代数効果は、ディスパッチャのような情報をどこかに送信できる概念です。アイデアは、特定の関数を呼び出して、現在実行中の関数を正確な位置で中断し、親関数に計算を処理させることです。親計算が終了すると、情報が送信された初期位置にプログラムを再開できます。

OCamlやEffなどの一部の言語は、これらの機能をネイティブに利用できます。実装の詳細は親のみに依存しているため、これは非常に興味深い抽象化です。

JavaScriptにこのような機能があるとすばらしいと思いませんか?

Reactチームは、JavaScriptのtry / catchブロックを処理するReactコンテキストで同様のアプローチを作成しました。 Dailによると、JavaScriptで最も近い概念です。

何かを投げると、どこかに親に情報を送信できます。情報をキャッチした最初の親は、その情報を処理して計算することができます。

例は千の言葉よりも良い

同期APIを使用してフシギダネを取得しようとする次のコードを想像してください。

同期APIを使用してデータを取得することはあまり一般的ではないため、このコードは奇妙な場合があります。 customFetch関数の実装の内部にジャンプしましょう。

あ、待って!これは絶対にフェッチのようには見えません!この関数が何を目的としているのかまったくわかりません…

さて、コンポーネントの周りに何かを想像してみてください。

コードを読むのに時間がかかります。

それでは、customFetchの実装にジャンプしましょう。

前のスニペットの重要な部分は、try / catchブロックです。

これらのさまざまなコードで何が起こっているのかをまとめましょう。

  • PokemonコンポーネントはcustomFetchメソッドを呼び出します。
  • customFetchメソッドは内部キャッシュを読み取ろうとしますが、空です。だから、何か/どこかを投げる-代数的効果。
  • ファイバーの親は、その情報をキャッチして処理し、データを取得します。次に、customFetchキャッシュにデータを取り込みます。
  • Component(args)で再レンダリングが行われ、customFetchキャッシュがいっぱいになりました。データは、同期APIを使用してコンポーネントで利用可能になりました。

反応キャッシュの実装の詳細を見て、さまざまなスローを確認してください。

このプロセス中に何か注意を引いた可能性があります。renderが2回呼び出されました。 1つはエラーをスローするため、1つはコンポーネントを一時停止するため、もう1つはデータを取得するために、コンポーネントを再開します。 Reactは純粋な機能にすぎないため、複数のレンダーコールをトリガーしてもかまいません。副作用はありません。

待って…何?レンダリングには副作用がありませんか? DOMはどうですか?

反応段階

Reactを長期間使用している場合、複数回再レンダリングするのは良い習慣ではないと聞いたことがあるかもしれません。ファイバーアーキテクチャの前は、React関数を呼び出すたびに、Reactは内部計算を行い、それに応じてDOMを変更していました。たとえば、これはsetStateを介してレンダー関数を呼び出すときに発生しました。プロセスはインライン化されました:

setState→レンダリング→仮想ノードの比較→DOMノードの更新

繊維を扱う場合、プロセスは少し異なります。高性能のDOM変更を可能にするキューとバッチの概念を導入しました。

アイデアは非常に単純です。スクリーンは毎秒最大60フレームを実行できると想定しています。この仮定から、利用可能なJavaScript関数を使用すると、16.7ミリ秒ごとにのみ計算とDOMの変更を行うことができます。ファイバーを使用すると、Reactは複数の変更をキューに登録し、それらを1秒間に約60回コミットできます。

このような変更により、Reactは独自の利点と特殊性を備えた3つのフェーズに分割できました。

Reactフェーズに関するDan Abramov
  • レンダリングフェーズは純粋で決定的です。副作用はなく、構成されているさまざまな機能を複数回呼び出すことができます。レンダリングフェーズは中断可能です。一時停止モードにあるのはレンダリング機能ではなく、フェーズ全体です
  • コミット前のフレーズは、スクロールバーの位置など、読み取りモードで実際のDOM状態へのアクセスを提供することを目的としています。
  • コミットフェーズは実際にDOMを変更し、割り込みはできません。 Reactはそのフェーズ中に一時停止できません。

この3つのフェーズのセットにより、タイムスライス機能が導入されました。 Reactは、レンダリングフェーズ中、2つのコンポーネント関数呼び出しの間で一時停止し、必要に応じてそのフェーズを再開できます。

ファイバーでは、レンダーは内部状態に基づいてコンポーネントの最新の利用可能な表現を取得することのみを目的としており、いくつかの比較を行い、ReactがDOMを変更する必要があるかどうかを確認します。コミットの変更が必要な場合、変更を「進行中の作業」キューに追加します。

Reactチームは、React Concurrent(Time Slicing + Suspense)とファイバーアーキテクチャのおかげで、パフォーマンスを大幅に改善しました。イベントの優先順位付けや同時実行性など、さまざまなブラウザーの問題に対処するための回避策を作成しました。

一歩後退すると、それは彼らが示したものではありませんか?優先順位付けは、ブラウザおよびフロントエンドフレームワークの新しい課題のようです。

他のチームも実際の最新技術の改善に取り組んでおり、将来のAPIを提案しています。これはGoogleの見解です。