// jQuery 示例 $("ol li").click(function() {}); let li = $("<li>我是一个列表项</li>"); $("ol").append(li);这种方式虽然直观,但随着应用复杂度的提高,代码管理变得越来越困难。
// React组件示例 const Component = ( <ul> {data.map(item => <MyItem data={item} />)} </ul> );声明式编程使得组件化开发成为可能,极大地提高了代码的可维护性和可扩展性。
// 利用DocumentFragment优化DOM操作 const fragment = document.createDocumentFragment(); for(let i = 0; i < 1000; i++) { const div = document.createElement('div'); fragment.appendChild(div); } const container = document.getElementById('container'); container.appendChild(fragment);轻量级JavaScript操作进行DOM差异化(Diffing):避免过度查询和存储实际DOM,提高性能。
<!-- 堆代码 duidaima.com --> <script> let count = 0; function handleClick() { count += 1; } $: { console.log(`当前计数为 ${count}`); } </script> <div class="x-three-year" on:click={handleClick}> <div class="no-open" style={{ color: 'blue' }}>{`计数: ${count}`}</div> </div>在这个示例中,我们通过基本声明获得了一个响应性变量。然后,通过将其绑定到点击事件,我们得到了一个通过点击驱动视图数据的普通组件。
function instance($$self, $$props, $$invalidate) { let count = 0; function handleClick() { $$invalidate(0, count += 1); } $$self.$$.update = () => { if ($$self.$$.dirty & /*count*/ 1) { $: { console.log(`当前计数为 ${count}`); } } }; return [count, handleClick]; }Svelte的优势
export function createSignal<T>( value?: T, options?: SignalOptions<T | undefined> ): Signal<T | undefined> { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s: SignalState<T | undefined> = { value, observers: null, observerSlots: null, comparator: options.equals || undefined }; if ("_SOLID_DEV_" && !options.internal) { if (options.name) s.name = options.name; registerGraph(s); } const setter: Setter<T | undefined> = (value?: unknown) => { if (typeof value === "function") { if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue); else value = value(s.value); } return writeSignal(s, value); }; return [readSignal.bind(s), setter]; }SignalState 和 Computation 的角色
export interface SignalState<T> extends SourceMapValue { value: T; observers: Computation<any>[] | null; observerSlots: number[] | null; tValue?: T; comparator?: (prev: T, next: T) => boolean; }Computation:在全局作用域中,有一个Listener用来临时存储类型为Computation的观察者。在组件渲染(createRenderEffect)或调用createEffect时,通过updateComputation方法为全局Listener赋值,为后续的依赖跟踪打下基础。
let Listener: Computation<any> | null = null; export interface Computation<Init, Next extends Init = Init> extends Owner { fn: EffectFunction<Init, Next>; state: ComputationState; tState?: ComputationState; sources: SignalState<Next>[] | null; sourceSlots: number[] | null; value?: Init; updatedAt: number | null; pure: boolean; user?: boolean; suspense?: SuspenseContextType; } function updateComputation(node: Computation<any>) { if (!node.fn) return; cleanNode(node); const owner = Owner, listener = Listener, time = ExecCount; Listener = Owner = node; runComputation( node, Transition && Transition.running && Transition.sources.has(node as Memo<any>) ? (node as Memo<any>).tValue : node.value, time ); //... Listener = listener; Owner = owner; }由于信号的读取,通过函数调用获取数据。
<div class="no-open" style={{ color: 'blue' }}>{`count: ${count()}`}</div>信号的读取和写入
export function readSignal(this: SignalState<any> | Memo<any>) { const runningTransition = Transition && Transition.running; if ( (this as Memo<any>).sources && (runningTransition ? (this as Memo<any>).tState : (this as Memo<any>).state) ) { if ((runningTransition ? (this as Memo<any>).tState : (this as Memo<any>).state) === STALE) updateComputation(this as Memo<any>); else { const updates = Updates; Updates = null; runUpdates(() => lookUpstream(this as Memo<any>), false); Updates = updates; } } if (Listener) { const sSlot = this.observers ? this.observers.length : 0; if (!Listener.sources) { Listener.sources = [this]; Listener.sourceSlots = [sSlot]; } else { Listener.sources.push(this); Listener.sourceSlots!.push(sSlot); } if (!this.observers) { this.observers = [Listener]; this.observerSlots = [Listener.sources.length - 1]; } else { this.observers.push(Listener); this.observerSlots!.push(Listener.sources.length - 1); } } if (runningTransition && Transition!.sources.has(this)) return this.tValue; return this.value; }写入信号:写入信号时,调用writeSignal函数。在闭包内更改当前SignalState后,遍历在readSignal阶段收集的观察者数组,并将观察者推入当前Effect执行列表。
export function writeSignal(node: SignalState<any> | Memo<any>, value: any, isComp?: boolean) { let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value; if (!node.comparator || !node.comparator(current, value)) { if (Transition) { const TransitionRunning = Transition.running; if (TransitionRunning || (!isComp && Transition.sources.has(node))) { Transition.sources.add(node); .tValue = value; } if (!TransitionRunning) node.value = value; } else node.value = value; if (node.observers && node.observers.length) { runUpdates(() => { for (let i = 0; i < node.observers!.length; i += 1) { const o = node.observers![i]; const TransitionRunning = Transition && Transition.running; if (TransitionRunning && Transition!.disposed.has(o)) continue; if (TransitionRunning ? !o.tState : !o.state) { if (o.pure) Updates!.push(o); else Effects!.push(o); if ((o as Memo<any>).observers) markDownstream(o as Memo<any>); } if (!TransitionRunning) o.state = STALE; else o.tState = STALE; } if (Updates!.length > 10e5) { Updates = []; if ("_SOLID_DEV_") throw new Error("Potential Infinite Loop Detected."); throw new Error(); } }, false); } } return value; }消息重新分发:此时,Effect列表保存了当时的观察者。然后遍历并执行runEffects来重新分发消息。在相应的节点(Computation)中,重新执行readSignal函数,此时可以获取最新的数据结果。
function App() { const [count, setCount] = createSignal(0); return ( <div class="x-three-year" onClick={() => setCount((pre) => pre + 1)}> <div class="no-open">foobar</div> <div class="no-open">{count()}</div> </div> ); }在这个例子中,我们有一个计数器,每次点击时count会增加。这是通过setCount函数实现的,它是createSignal的一部分。
//no function Other({count}) { return ( <div> <div>{count}</div> </div> ); } //yes function Other(props) { return ( <div> <div>{props.count}</div> </div> ); } function App() { const [count, setCount] = createSignal(0); return ( <div class="x-three-year" onClick={() => setCount((pre: any) => pre + 1)}> <div class="no-open">foobar</div> <div class="no-open">{count()}</div> <Other count={count()}></Other> </div> ); } //yes function Other({count}) { return ( <div> <div>{count()}</div> </div> ); } function App() { const [count, setCount] = createSignal(0); return ( <div class="x-three-year" onClick={() => setCount((pre: any) => pre + 1)}> <div class="no-open">foobar</div> <div class="no-open">{count()}</div> <Other count={count}></Other> </div> ); }同时,Solid官方也提供了像mergeProps和splitProps这样的API,用于子组件修改响应式props数据。内部它使用Proxy代理来实现动态跟踪。
const [count, setCount] = createSignal(100); createEffect(() => { setTimeout(() => { // 这种方式无法跟踪 console.log('count', count()); }, 100); });这是因为当readSignal函数在这个时候读取Listener时,基本过程已经完成,数据已被清除(Listener = null Owner = null),因此在读取时无法跟踪SignalState。
createEffect(() => { const tempCount = count(); setTimeout(() => { console.log('count', tempCount; }, 100); });