一体、反応性とは!?

Dec 22, 2024 am 05:44 AM

反応性モデルの説明

序文

私がアプリケーションと 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}`;
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

WTF Is Reactivity !?

リアクティブ ライブラリ (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 の再計算);

WTF Is Reactivity !?

このシステムの課題の 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 の再計算);

WTF Is Reactivity !?

再び「ダイヤモンド問題」が発生します...今回はデータ ソース C 上で 2 回評価される可能性があり、常に D 上で評価されます。

ダイヤモンドの問題

「ダイヤモンド問題」は、「熱心な」反応性モデルにおける新しい課題ではありません。一部の計算アルゴリズム (特に MobX で使用されるアルゴリズム) は、状態計算を平準化するために「リアクティブな依存関係ツリーのノード」にタグを付けることができます。このアプローチでは、システムは最初に「ルート」データ ソース (この例では A と E)、次に B と C、最後に D を評価します。状態計算の順序を変更すると、この種の問題の解決に役立ちます。

WTF Is Reactivity !?

PULL、別名「怠惰な」反応性

2 番目の反応性モデルは 「PULL」 と呼ばれます。 "PUSH" モデルとは異なり、次の原則に基づいています:

  • リアクティブ変数の宣言
  • システムは状態の計算を延期します
  • 派生状態は依存関係に基づいて計算されます
  • システムは過剰なアップデートを回避します

覚えておくべき最も重要なのはこの最後のルールです。前のシステムとは異なり、この最後のルールは、同じデータ ソースの複数の評価を避けるために状態の計算を延期します。

1 番目の使用例: 初期状態と状態変化

前の初期状態を保持しましょう...

WTF Is Reactivity !?

この種のシステムでは、初期状態の構文は次の形式になります。

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 の値を計算します);

WTF Is Reactivity !?

依存関係をクエリするときに、このシステムの最適化が可能です。実際、上記のシナリオでは、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 の値をフェッチします。

WTF Is Reactivity !?

A の値は変更されていないため、この変数を再計算する必要はありません (同じことが B の値にも当てはまります)。このような場合、メモ化アルゴリズムを使用すると、状態計算中のパフォーマンスが向上します。

プッシュプル、別名「きめの細かい」反応性

最後の反応性モデルは、「PUSH-PULL」システムです。 「PUSH」という用語は変更通知の即時伝達を反映し、「PULL」はオンデマンドで状態値をフェッチすることを指します。このアプローチは、以下の原則に従う、いわゆる「きめの細かい」反応性と密接に関連しています。

  • リアクティブ変数の宣言 (リアクティブ プリミティブについて話しています)
  • 依存関係は原子レベルで追跡されます
  • 変更の伝播は非常に的を絞ったものです

この種の反応性は「PUSH-PULL」モデルに限定されたものではないことに注意してください。きめ細かい反応性とは、システムの依存関係を正確に追跡することを指します。したがって、この方法でも機能する PUSHPULL 反応性モデルがあります (私は 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 を評価します。これは直感的な操作モードではないでしょうか?

WTF Is Reactivity !?

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 の値を取得します。

WTF Is Reactivity !?

これは、このモデルを非常に効率的にする、リアクティブな依存関係の相互の事前の関連付けです!

実際、古典的な "PULL" システム (React の Virtual DOM など) では、コンポーネントからリアクティブ状態を更新すると、フレームワークに変更が通知されます (" 違います」フェーズ)。次に、オンデマンド (および遅延) で、フレームワークはリアクティブな依存関係ツリーを走査して変更を計算します。変数が更新されるたびに!この依存関係の状態の「発見」には多大なコストがかかります...

「きめ細かい」リアクティブ システム (シグナルなど) を使用すると、リアクティブ変数/プリミティブの更新により、それらにリンクされている派生状態に変更が自動的に通知されます。したがって、関連する依存関係を (再) 検出する必要はありません。状態の伝播がターゲットです!

結論(.value)

2024 年、ほとんどの Web フレームワークは、特に反応性モデルの観点から、その動作方法を再考することを選択しました。この変化により、企業の効率性と競争力は全般的に向上しました。他の人は (まだ) ハイブリッド (ここでは Vue について考えています) を選択しており、これにより多くの状況でより柔軟になります。

最後に、どのモデルを選択しても、私の意見では、(良い) リアクティブ システムはいくつかの主要なルールに基づいて構築されます。

  1. システムは、一貫性のない派生 状態を防止します。
  2. システム内で状態を使用すると、リアクティブに派生した状態が生成されます。
  3. システムは過剰な作業を最小限に抑えます ;
  4. そして、「与えられた初期状態について、状態がたどる経路に関係なく、システムの最終結果は常に同じになります! 「

この最後の点は、宣言型プログラミングの基本原則として解釈できますが、(良い) リアクティブ システムは決定論的である必要があると私がどのように考えているかを示しています。これは、アルゴリズムの複雑さに関係なく、リアクティブ モデルを信頼性が高く、予測可能で、大規模な技術プロジェクトで使いやすくする「決定論」です。

以上が一体、反応性とは!?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

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

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

フロントエンドのサーマルペーパーレシートのために文字化けしたコード印刷に遭遇した場合はどうすればよいですか? フロントエンドのサーマルペーパーレシートのために文字化けしたコード印刷に遭遇した場合はどうすればよいですか? Apr 04, 2025 pm 02:42 PM

フロントエンドのサーマルペーパーチケット印刷のためのよくある質問とソリューションフロントエンド開発におけるチケット印刷は、一般的な要件です。しかし、多くの開発者が実装しています...

javascriptの分解:それが何をするのか、なぜそれが重要なのか javascriptの分解:それが何をするのか、なぜそれが重要なのか Apr 09, 2025 am 12:07 AM

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

誰がより多くのPythonまたはJavaScriptを支払われますか? 誰がより多くのPythonまたはJavaScriptを支払われますか? Apr 04, 2025 am 12:09 AM

スキルや業界のニーズに応じて、PythonおよびJavaScript開発者には絶対的な給与はありません。 1. Pythonは、データサイエンスと機械学習でさらに支払われる場合があります。 2。JavaScriptは、フロントエンドとフルスタックの開発に大きな需要があり、その給与もかなりです。 3。影響要因には、経験、地理的位置、会社の規模、特定のスキルが含まれます。

Shiseidoの公式Webサイトのように、視差スクロールと要素のアニメーション効果を実現する方法は?
または:
Shiseidoの公式Webサイトのようにスクロールするページを伴うアニメーション効果をどのように実現できますか? Shiseidoの公式Webサイトのように、視差スクロールと要素のアニメーション効果を実現する方法は? または: Shiseidoの公式Webサイトのようにスクロールするページを伴うアニメーション効果をどのように実現できますか? Apr 04, 2025 pm 05:36 PM

この記事の視差スクロールと要素のアニメーション効果の実現に関する議論では、Shiseidoの公式ウェブサイト(https://www.shisido.co.co.jp/sb/wonderland/)と同様の達成方法について説明します。

JavaScriptは学ぶのが難しいですか? JavaScriptは学ぶのが難しいですか? Apr 03, 2025 am 12:20 AM

JavaScriptを学ぶことは難しくありませんが、挑戦的です。 1)変数、データ型、関数などの基本概念を理解します。2)非同期プログラミングをマスターし、イベントループを通じて実装します。 3)DOM操作を使用し、非同期リクエストを処理することを約束します。 4)一般的な間違いを避け、デバッグテクニックを使用します。 5)パフォーマンスを最適化し、ベストプラクティスに従ってください。

JavaScriptの進化:現在の傾向と将来の見通し JavaScriptの進化:現在の傾向と将来の見通し Apr 10, 2025 am 09:33 AM

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

JavaScriptを使用して、同じIDを持つArray要素を1つのオブジェクトにマージする方法は? JavaScriptを使用して、同じIDを持つArray要素を1つのオブジェクトにマージする方法は? Apr 04, 2025 pm 05:09 PM

同じIDを持つ配列要素をJavaScriptの1つのオブジェクトにマージする方法は?データを処理するとき、私たちはしばしば同じIDを持つ必要性に遭遇します...

フロントエンド開発でVSCodeと同様に、パネルドラッグアンドドロップ調整機能を実装する方法は? フロントエンド開発でVSCodeと同様に、パネルドラッグアンドドロップ調整機能を実装する方法は? Apr 04, 2025 pm 02:06 PM

フロントエンドのVSCodeと同様に、パネルドラッグアンドドロップ調整機能の実装を調べます。フロントエンド開発では、VSCODEと同様のVSCODEを実装する方法...

See all articles