反応性モデルの説明
序文
私がアプリケーションと Web サイトの開発を始めてから (すでに) 10 年になりますが、JavaScript エコシステムが今日ほどエキサイティングなものはありません!
2022 年、コミュニティは「Signal」のコンセプトに魅了され、ほとんどの JavaScript フレームワークがそれを独自のエンジンに統合しました。私は Preact について考えています。Preact は 2022 年 9 月以来、コンポーネントのライフサイクルから切り離されたリアクティブ変数を提供しています。最近では、2023 年 5 月に実験的に Signals を実装した Angular が、バージョン 18 から正式に開始されました。他の JavaScript ライブラリもアプローチを再考することを選択しました...
2023 年から現在まで、私はさまざまなプロジェクトで一貫して Signals を使用してきました。その実装と使用のシンプルさは私を完全に納得させ、技術ワークショップ、トレーニング セッション、カンファレンス中にそのメリットを専門家ネットワークと共有しました。
しかし最近、このコンセプトは本当に「革命的」なのか、Signals に代わるものはあるのか、と自問し始めました。そこで、私はこの考察をさらに深く掘り下げ、リアクティブ システムへのさまざまなアプローチを発見しました。
この投稿は、さまざまな反応性モデルの概要と、それらがどのように機能するかについての私の理解を示しています。
注意: この時点では、おそらくお察しのとおり、Java の「Reactive Streams」については説明しません。そうでなければ、この投稿のタイトルを「WTF Is Backpressure!?」 ?
にしていたでしょう。理論
反応性モデルについて話すときは、(何よりもまず) "反応性プログラミング" について話しますが、特に "反応性" について話します。
リアクティブ プログラミングは、データ ソースの変更を消費者に自動的に伝達できる開発パラダイムです。
したがって、反応性を、データの変更に応じてリアルタイムで依存関係を更新する機能として定義できます。
NB: つまり、ユーザーがフォームに入力したり送信したりすると、これらの変更に反応し、読み込みコンポーネント、または何かが起こっていることを示すその他の表示を行う必要があります。 .. 別の例として、データを非同期で受信する場合、このデータのすべてまたは一部を表示したり、新しいアクションを実行したりするなどの対応が必要です。
これに関連して、リアクティブ ライブラリは自動的に更新され、効率的に伝播する変数を提供するため、シンプルで最適化されたコードを簡単に作成できます。
効率を高めるために、これらのシステムは、値が変更された場合に限り、これらの変数を再計算/再評価する必要があります。同様に、ブロードキャストされたデータの一貫性と最新性を確保するために、システムは中間状態 (特に状態変化の計算中) を表示しないようにする必要があります。
NB: 状態とは、プログラム/アプリケーションの存続期間全体を通じて使用されるデータ/値を指します。
わかりました、しかしそれでは…これらの "反応性モデル" とは一体何なのでしょうか?
PUSH、別名「熱心な」反応性
最初の反応性モデルは、「PUSH」 (または「熱心な」反応性) と呼ばれます。このシステムは次の原則に基づいています:
- データ ソースの初期化 (「Observables」として知られています)
- コンポーネント/関数はこれらのデータ ソースをサブスクライブします (これらはコンシューマーです)
- 値が変更されると、データはすぐにコンシューマー (「オブザーバー」として知られています) に伝播されます
ご想像のとおり、「PUSH」モデルは「Observable/Observer」設計パターンに依存しています。
1 番目の使用例: 初期状態と状態変化
次の初期状態を考えてみましょう。
let a = { firstName: "John", lastName: "Doe" }; const b = a.firstName; const c = a.lastName; const d = `${b} ${c}`;
リアクティブ ライブラリ (RxJS など) を使用すると、この初期状態は次のようになります。
let a = observable.of({ firstName: "John", lastName: "Doe" }); const b = a.pipe(map((a) => a.firstName)); const c = a.pipe(map((a) => a.lastName)); const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
注意: この投稿では、すべてのコード スニペットを「疑似コード」と見なす必要があります。
ここで、コンシューマ (コンポーネントなど) が、このデータ ソースが更新されるたびに状態 D の値をログに記録したいと仮定します。
d.subscribe((value) => console.log(value));
私たちのコンポーネントはデータ ストリームをサブスクライブします。まだ変化を引き起こす必要があります。
a.next({ firstName: "Jane", lastName: "Doe" });
そこから、「PUSH」システムが変更を検出し、自動的に消費者にブロードキャストします。上記の初期状態に基づいて、発生する可能性のある操作を以下に説明します。
- データ ソース A で状態変化が発生しました!
- A の値は B に伝播されます (データ ソース B の計算);
- 次に、B の値が D に伝播されます (データ ソース D の計算);
- A の値は C に伝播されます (データ ソース C の計算);
- 最後に、C の値が D に伝播されます (データ ソース D の再計算);
このシステムの課題の 1 つは計算の順序にあります。実際、私たちのユースケースに基づくと、D は 2 回評価される可能性があることがわかります。1 回目は前の状態の C の値で、2 回目は前の状態の C の値で評価されます。 2 回目は C の値が最新です。この種の反応性モデルでは、この課題は 「ダイヤモンド問題」 ♦️.
と呼ばれます。2 番目の使用例: 次の反復
次に、状態が 2 つの主要なデータ ソースに依存していると仮定します。
let a = { firstName: "John", lastName: "Doe" }; const b = a.firstName; const c = a.lastName; const d = `${b} ${c}`;
E を更新するとき、システムは状態全体を再計算します。これにより、前の状態を上書きすることで、信頼できる単一の情報源を保持できます。
- データ ソース E で状態変化が発生しました!
- A の値は B に伝播されます (データ ソース B の計算);
- 次に、B の値が D に伝播されます (データ ソース D の計算);
- A の値は C に伝播されます (データ ソース C の計算);
- E の値は C に伝播されます (データ ソース C の再計算)。
- 最後に、C の値が D に伝播されます (データ ソース D の再計算);
再び「ダイヤモンド問題」が発生します...今回はデータ ソース C 上で 2 回評価される可能性があり、常に D 上で評価されます。
ダイヤモンドの問題
「ダイヤモンド問題」は、「熱心な」反応性モデルにおける新しい課題ではありません。一部の計算アルゴリズム (特に MobX で使用されるアルゴリズム) は、状態計算を平準化するために「リアクティブな依存関係ツリーのノード」にタグを付けることができます。このアプローチでは、システムは最初に「ルート」データ ソース (この例では A と E)、次に B と C、最後に D を評価します。状態計算の順序を変更すると、この種の問題の解決に役立ちます。
PULL、別名「怠惰な」反応性
2 番目の反応性モデルは 「PULL」 と呼ばれます。 "PUSH" モデルとは異なり、次の原則に基づいています:
- リアクティブ変数の宣言
- システムは状態の計算を延期します
- 派生状態は依存関係に基づいて計算されます
- システムは過剰なアップデートを回避します
覚えておくべき最も重要なのはこの最後のルールです。前のシステムとは異なり、この最後のルールは、同じデータ ソースの複数の評価を避けるために状態の計算を延期します。
1 番目の使用例: 初期状態と状態変化
前の初期状態を保持しましょう...
この種のシステムでは、初期状態の構文は次の形式になります。
let a = observable.of({ firstName: "John", lastName: "Doe" }); const b = a.pipe(map((a) => a.firstName)); const c = a.pipe(map((a) => a.lastName)); const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
注意: React 愛好家はおそらくこの構文を認識しているでしょう ?
リアクティブ変数を宣言すると、タプルが「誕生」します。一方は不変変数です。もう一方のこの変数の更新関数。残りのステートメント (この場合は B、C、D) は、それぞれの依存関係を「リッスン」するため、派生状態とみなされます。
d.subscribe((value) => console.log(value));
「遅延」システムの決定的な特徴は、変更がすぐには反映されず、明示的に要求された場合にのみ反映されることです。
let a = { firstName: "John", lastName: "Doe" }; const b = a.firstName; const c = a.lastName; const d = `${b} ${c}`;
「PULL」モデルでは、(コンポーネントから) エフェクト() を使用してリアクティブ変数 (依存関係として指定) の値を記録すると、状態変化の計算がトリガーされます。
- D は、その依存関係 (B および C) が更新されたかどうかを確認します。
- B は、その依存関係 (A) が更新されたかどうかを確認します。
- A はその値を B に伝播します (B の値を計算します);
- C は、その依存関係 (A) が更新されたかどうかを確認します。
- A はその値を C に伝播します (C の値を計算します)
- B と C はそれぞれの値を D に伝播します (D の値を計算します);
依存関係をクエリするときに、このシステムの最適化が可能です。実際、上記のシナリオでは、A が更新されたかどうかを判断するために 2 回クエリが実行されます。ただし、状態が変化したかどうかを定義するには、最初のクエリで十分な場合があります。 C はこのアクションを実行する必要はありません...代わりに、A はその値をブロードキャストすることのみが可能です。
2 番目の使用例: 次の反復
2 番目のリアクティブ変数「root」を追加して、状態をいくらか複雑にしてみましょう。
let a = observable.of({ firstName: "John", lastName: "Doe" }); const b = a.pipe(map((a) => a.firstName)); const c = a.pipe(map((a) => a.lastName)); const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
もう一度言いますが、システムは明示的に要求されるまで状態の計算を延期します。以前と同じ効果を使用して、新しいリアクティブ変数を更新すると、次の手順がトリガーされます:
- D は、その依存関係 (B および C) が更新されたかどうかを確認します ;
- B は、その依存関係 (A) が更新されたかどうかを確認します ;
- C は、その依存関係 (A および E) が更新されたかどうかを確認します ;
- E はその値を C に伝播し、C はメモ化 (C の値の計算) を介して A の値をフェッチします。
- C はその値を D に伝播し、D はメモ化 (D の値の計算) を介して B の値をフェッチします。
A の値は変更されていないため、この変数を再計算する必要はありません (同じことが B の値にも当てはまります)。このような場合、メモ化アルゴリズムを使用すると、状態計算中のパフォーマンスが向上します。
プッシュプル、別名「きめの細かい」反応性
最後の反応性モデルは、「PUSH-PULL」システムです。 「PUSH」という用語は変更通知の即時伝達を反映し、「PULL」はオンデマンドで状態値をフェッチすることを指します。このアプローチは、以下の原則に従う、いわゆる「きめの細かい」反応性と密接に関連しています。
- リアクティブ変数の宣言 (リアクティブ プリミティブについて話しています)
- 依存関係は原子レベルで追跡されます
- 変更の伝播は非常に的を絞ったものです
この種の反応性は「PUSH-PULL」モデルに限定されたものではないことに注意してください。きめ細かい反応性とは、システムの依存関係を正確に追跡することを指します。したがって、この方法でも機能する PUSH と PULL 反応性モデルがあります (私は Jotai または Recoil について考えています。
1 番目の使用例: 初期状態と状態変化
まだ前の初期状態に基づいています...「きめの細かい」反応性システムにおける初期状態の宣言は次のようになります:
let a = { firstName: "John", lastName: "Doe" }; const b = a.firstName; const c = a.lastName; const d = `${b} ${c}`;
注意: シグナル キーワードの使用は、ここでの単なる逸話ではありません ?
構文の点では、「PUSH」モデルに非常に似ていますが、注目すべき重要な違いが 1 つあります: 依存関係! 「きめ細かい」反応性システム、派生状態は使用する変数を暗黙的に追跡するため、派生状態の計算に必要な依存関係を明示的に宣言する必要はありません。この場合、B と C は A の値への変更を自動的に追跡し、D は B と C の両方への変更を追跡します。
let a = observable.of({ firstName: "John", lastName: "Doe" }); const b = a.pipe(map((a) => a.firstName)); const c = a.pipe(map((a) => a.lastName)); const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
このようなシステムでは、リアクティブ変数の更新は、基本的な "PUSH" モデルよりも効率的です。これは、変更がそれに依存する派生変数に自動的に伝播されるためです (通知としてのみ、伝播されません)。値そのもの)。
d.subscribe((value) => console.log(value));
次に、オンデマンド (ロガー の例を見てみましょう) に応じて、システム内で D を使用すると、関連するルート状態 (この場合は A) の値がフェッチされ、値が計算されます。派生状態 (B と C) を計算し、最後に D を評価します。これは直感的な操作モードではないでしょうか?
2 番目の使用例: 次の反復
次の状態を考えてみましょう。
a.next({ firstName: "Jane", lastName: "Doe" });
もう一度言いますが、PUSH-PULL システムの「きめ細かい」側面により、各状態の自動追跡が可能になります。したがって、派生状態 C はルート状態 A と E を追跡するようになりました。変数 E を更新すると、次のアクションがトリガーされます:
- リアクティブプリミティブ E の状態変化!
- 対象を絞った変更通知 (C を介して E から D);
- E はその値を C に伝播し、C はメモ化 (C の値の計算) を介して A の値を取得します。
- C はその値を D に伝播し、D はメモ化 (D の値の計算) を介して B の値を取得します。
これは、このモデルを非常に効率的にする、リアクティブな依存関係の相互の事前の関連付けです!
実際、古典的な "PULL" システム (React の Virtual DOM など) では、コンポーネントからリアクティブ状態を更新すると、フレームワークに変更が通知されます (" 違います」フェーズ)。次に、オンデマンド (および遅延) で、フレームワークはリアクティブな依存関係ツリーを走査して変更を計算します。変数が更新されるたびに!この依存関係の状態の「発見」には多大なコストがかかります...
「きめ細かい」リアクティブ システム (シグナルなど) を使用すると、リアクティブ変数/プリミティブの更新により、それらにリンクされている派生状態に変更が自動的に通知されます。したがって、関連する依存関係を (再) 検出する必要はありません。状態の伝播がターゲットです!
結論(.value)
2024 年、ほとんどの Web フレームワークは、特に反応性モデルの観点から、その動作方法を再考することを選択しました。この変化により、企業の効率性と競争力は全般的に向上しました。他の人は (まだ) ハイブリッド (ここでは Vue について考えています) を選択しており、これにより多くの状況でより柔軟になります。
最後に、どのモデルを選択しても、私の意見では、(良い) リアクティブ システムはいくつかの主要なルールに基づいて構築されます。
- システムは、一貫性のない派生 状態を防止します。
- システム内で状態を使用すると、リアクティブに派生した状態が生成されます。
- システムは過剰な作業を最小限に抑えます ;
- そして、「与えられた初期状態について、状態がたどる経路に関係なく、システムの最終結果は常に同じになります! 「
この最後の点は、宣言型プログラミングの基本原則として解釈できますが、(良い) リアクティブ システムは決定論的である必要があると私がどのように考えているかを示しています。これは、アルゴリズムの複雑さに関係なく、リアクティブ モデルを信頼性が高く、予測可能で、大規模な技術プロジェクトで使いやすくする「決定論」です。
以上が一体、反応性とは!?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

はじめに私はあなたがそれを奇妙に思うかもしれないことを知っています、JavaScript、C、およびブラウザは正確に何をしなければなりませんか?彼らは無関係であるように見えますが、実際、彼らは現代のウェブ開発において非常に重要な役割を果たしています。今日は、これら3つの間の密接なつながりについて説明します。この記事を通して、JavaScriptがブラウザでどのように実行されるか、ブラウザエンジンでのCの役割、およびそれらが協力してWebページのレンダリングと相互作用を駆動する方法を学びます。私たちは皆、JavaScriptとブラウザの関係を知っています。 JavaScriptは、フロントエンド開発のコア言語です。ブラウザで直接実行され、Webページが鮮明で興味深いものになります。なぜJavascrを疑問に思ったことがありますか

node.jsは、主にストリームのおかげで、効率的なI/Oで優れています。 ストリームはデータを段階的に処理し、メモリの過負荷を回避します。大きなファイル、ネットワークタスク、リアルタイムアプリケーションの場合。ストリームとTypeScriptのタイプの安全性を組み合わせることで、パワーが作成されます

PythonとJavaScriptのパフォーマンスと効率の違いは、主に以下に反映されています。1)解釈された言語として、Pythonはゆっくりと実行されますが、開発効率が高く、迅速なプロトタイプ開発に適しています。 2)JavaScriptはブラウザ内の単一のスレッドに限定されていますが、マルチスレッドおよび非同期I/Oを使用してnode.jsのパフォーマンスを改善でき、両方とも実際のプロジェクトで利点があります。

JavaScriptは1995年に発信され、Brandon Ikeによって作成され、言語をCに実現しました。 2。JavaScriptのメモリ管理とパフォーマンスの最適化は、C言語に依存しています。 3. C言語のクロスプラットフォーム機能は、さまざまなオペレーティングシステムでJavaScriptを効率的に実行するのに役立ちます。

JavaScriptはブラウザとnode.js環境で実行され、JavaScriptエンジンに依存してコードを解析および実行します。 1)解析段階で抽象的構文ツリー(AST)を生成します。 2)ASTをコンパイル段階のバイトコードまたはマシンコードに変換します。 3)実行段階でコンパイルされたコードを実行します。

PythonとJavaScriptの将来の傾向には、1。Pythonが科学コンピューティングの分野での位置を統合し、AI、2。JavaScriptはWebテクノロジーの開発を促進します。どちらもそれぞれのフィールドでアプリケーションシナリオを拡大し続け、パフォーマンスをより多くのブレークスルーを行います。

開発環境におけるPythonとJavaScriptの両方の選択が重要です。 1)Pythonの開発環境には、Pycharm、Jupyternotebook、Anacondaが含まれます。これらは、データサイエンスと迅速なプロトタイピングに適しています。 2)JavaScriptの開発環境には、フロントエンドおよびバックエンド開発に適したnode.js、vscode、およびwebpackが含まれます。プロジェクトのニーズに応じて適切なツールを選択すると、開発効率とプロジェクトの成功率が向上する可能性があります。

はい、JavaScriptのエンジンコアはCで記述されています。1)C言語は、JavaScriptエンジンの開発に適した効率的なパフォーマンスと基礎となる制御を提供します。 2)V8エンジンを例にとると、そのコアはCで記述され、Cの効率とオブジェクト指向の特性を組み合わせて書かれています。3)JavaScriptエンジンの作業原理には、解析、コンパイル、実行が含まれ、C言語はこれらのプロセスで重要な役割を果たします。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

WebStorm Mac版
便利なJavaScript開発ツール

MinGW - Minimalist GNU for Windows
このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

SAP NetWeaver Server Adapter for Eclipse
Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

mPDF
mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

Safe Exam Browser
Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

ホットトピック









