闽公网安备 35020302035485号
// 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);
});


