Signals は、現在のフロントエンド フレームワークの選択においてはるかに先を行っています。
国慶節前の最後の週、私は新しいクラスメートの React コードをコード レビューしていたところ、彼がメモと useCallback を使用して、変更されたサブコンポーネント部分のみをレンダリングしたいことに気付きました。実際、この機能を React で実現するのは困難です。React の状態が変化した後、render 関数が再実行されるためです。つまり、コンポーネントで setState を呼び出した後、関数全体が再度実行されます。
React 自体ではそれができません。しかし、Signals ベースのフレームワークではこれが行われず、自動状態バインディングと依存関係追跡を通じて、現在の状態が変更された後にその状態を使用するコード ブロックのみが再実行されます。
私個人としては、当時この問題についてはあまり説明せず、React のレンダリングの仕組みについて簡単に説明しただけでした。シグナルの概要は次のとおりです。
アドバンテージ
React と比較すると、シグナルに基づくフレームワークの状態応答の粒度は非常に細かいです。ここではソリッドを例として取り上げます。
import { createSignal, onCleanup } from "solid-js";
const CountingComponent = () => {
// 创建一个 signal
const [count, setCount] = createSignal(0);
// 创建一个 signal
const [count2] = createSignal(666);
// 每一秒递增 1
const interval = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
// 组件销毁时清除定时器
onCleanup(() => clearInterval(interval));
return (
<div>
<div>
count: {count()}
{console.log("count is", count())}
</div>
<div>
count2: {count2()}
{console.log("count2 is", count2())}
</div>
</div>
);
};
上記のコードは、count が単独で変更された場合にのみ count を出力しますが、count2 のデータはまったく出力しません。
コンソールの出力は次のようになります。
- カウントは0です
- count2は666です
- カウントは1です
- カウントは2です
- ...
印刷結果から判断すると、Solid はレンダリング関数を最初に 1 回だけ実行し、その後は変更された DOM ノードのみをレンダリングします。これは React では不可能です。React はビュー駆動に基づいています。状態の変更によりレンダリング関数全体が再実行され、React は状態がどのように使用されるかを完全に認識できません。開発者は次のコードを通じて React を実装することもできます。再レンダリング。
const [, forceRender] = useReducer((s) => s + 1, 0);
きめ細かい更新に加えて、シグナルを使用するフレームワークのメンタル モデルもよりシンプルになります。最大の特徴は、開発者が状態がどこで定義されているか、対応する状態がどこでレンダリングされるかを気にする必要がないことです。次のように:
import { createSignal } from "solid-js";
// 把状态从过组件中提取出来
const [count, setCount] = createSignal(0);
const [count2] = createSignal(666);
setInterval(() => {
setCount((c) => c + 1);
}, 1000);
// 子组件依然可以使用 count 函数
const SubCountingComponent = () => {
return <div>{count()}</div>;
};
const CountingComponent = () => {
return (
<div>
<div>
count: {count()}
{console.log("count is", count())}
</div>
<div>
count2: {count2()}
{console.log("count2 is", count2())}
</div>
<SubCountingComponent />
</div>
);
};
上記のコードは引き続き正常に実行できます。それは国家主導だからです。開発者がコンポーネント内で Signal を使用する場合、それはローカル状態であり、コンポーネントの外部で Signal を定義する場合、それはグローバル状態です。
シグナル自体にはそれほど価値はありませんが、派生状態と副作用を組み合わせると価値があります。コードは次のようになります。
import {
createSignal,
onCleanup,
createMemo,
createEffect,
onMount,
} from "solid-js";
const [count, setCount] = createSignal(0);
setInterval(() => {
setCount((c) => c + 1);
}, 1000);
// 计算缓存
const doubleCount = createMemo(() => count() * 2);
// 基于当前缓存
const quadrupleCount = createMemo(() => doubleCount() * 2);
// 副作用
createEffect(() => {
// 在 count 变化时重新执行 fetch
fetch(`/api/${count()}`);
});
const CountingComponent = () => {
// 挂载组件时执行
onMount(() => {
console.log("start");
});
// 销毁组件时执行
onCleanup(() => {
console.log("end");
});
return (
<div>
<div>Count value is {count()}</div>
<div>doubleCount value is {doubleCount()}</div>
<div>quadrupleCount value is {quadrupleCount()}</div>
</div>
);
};
上記のコードからわかるように、派生状態も副作用も React のように依存関係を埋める必要がなく、副作用もライフサイクルから分離されています (コードが読みやすくなります)。
実装メカニズム
きめ細かく、高性能で、制限がありません。フロントエンド フレームワークの未来として評価されるに値します。では、それは具体的にどのように達成されるのでしょうか?
基本的に、シグナルは、アクセス時に依存関係を追跡し、変更時に副作用をトリガーする値のコンテナーです。
リアクティブな基底型に基づくこのパラダイムは、フロントエンドの世界では特に新しい概念ではありません。その起源は、Knockout observables や Meteor Tracker などの実装に遡り、10 年以上前に遡ります。Vue のオプション API も同じ原則に従いますが、基本型がオブジェクト プロパティの背後に隠されている点が異なります。このパラダイムに基づいて、Vue2 は最適化を必要とせずに非常に優れたパフォーマンスを発揮します。
依存関係のコレクション
React useState は現在の状態と設定値関数を返しますが、Solid の createSignal は 2 つの関数を返します。今すぐ:
type useState = (initial: any) => [state, setter];
type createSignal = (initial: any) => [getter, setter];
createSignal が対応する状態値を直接渡すのではなく、getter メソッドを渡す必要があるのはなぜですか? これは、フレームワークの応答性を高めるために、Signal がその値に関心のあるユーザーを収集する必要があるためです。単にステータスを渡すだけでは、Signal に関する情報を提供することはできません。getter メソッドは対応する値を返すだけでなく、実行中にサブスクリプションを作成してすべての依存関係情報を収集します。
テンプレートのコンパイル
Signals フレームワークの高いパフォーマンスを確保するには、この関数をテンプレートのコンパイルと組み合わせて実装する必要があります。フレームワーク開発者はテンプレートのコンパイルを通じて動的と静的な分離を実現し、依存関係の収集により、状態変数が変更されたときにポイントツーポイントの DOM 更新を実現できます。変化。したがって、現在の主流の Signals フレームワークは仮想 DOM を使用しません。仮想 DOM に基づいている Vue は、現在、同様の最適化を実現するためにコンパイラーに依存しています。
まず、Solid テンプレートのコンパイルを見てみましょう。
const CountingComponent = () => {
const [count, setCount] = createSignal(0);
const interval = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
onCleanup(() => clearInterval(interval));
return <div>Count value is {count()}</div>;
};
コンパイルされたコンポーネント コードに対応します。
const _tmpl$ = /*#__PURE__*/ _$template(`<div>Count value is `);
const CountingComponent = () => {
const [count, setCount] = createSignal(0);
const interval = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
onCleanup(() => clearInterval(interval));
return (() => {
const _el$ = _tmpl$(),
_el$2 = _el$.firstChild;
_$insert(_el$, count, null);
return _el$;
})();
};
- _tmpl$ 関数を実行して、対応するコンポーネントの静的テンプレートを取得します
- コンポーネント内の count 関数を抽出し、_$insert を通じて state 関数を対応するテンプレートの位置にバインドします。
- setCount 関数を呼び出して更新する場合は、対応するカウントを比較し、対応する _el$ 対応するデータを変更します。
他の
シグナルを使用する主流のフレームワークを見てみましょう。
ただし、現時点では React チームは Signals を使用しない可能性があるようです。
- シグナルはパフォーマンスは良好ですが、UI コードを記述するには適した方法ではありません
- コンパイラによるパフォーマンスの向上を計画
- シグナルなどのプリミティブを追加できます
PREACT 著者はReact 用のシグナルを提供するために@preact/signals-reactを書きました。ただし、個人的には実稼働環境での使用はお勧めしません。
スペースが限られているので、@preact/signals-coreのソースコードを後で解釈します。
参考文献
奨励する
この記事が良いと思われる場合は、励ましを与えて、私の github ブログにスターを付けるのにご協力いただければ幸いです。
オープンソース フレームワーク NanUI の作者がスチールの販売に切り替えたため、プロジェクトは中断されました。Apple App Store の無料リストのナンバー 1 はポルノ ソフトウェア TypeScript です。人気が出てきたばかりなのに、なぜ大手はそれを放棄し始めるのでしょうか。 ? TIOBE 10月リスト:Javaが最大の下落、C#はJavaに迫る Rust 1.73.0リリース AIガールフレンドにイギリス女王暗殺を勧められた男性に懲役9年の実刑判決 Qt 6.6正式リリース ロイター:RISC-Vテクノロジーが中米テクノロジー戦争の鍵となる 新たな戦場 RISC-V: 単一の企業や国に支配されない レノボ、Android PC の発売を計画