SolidJS — 终极综述
为什么现在还是 React,而不是 Solid.JS?
在本文中,我们将探讨 Solid.js 究竟是什么——是一次革命,是 React 的合格继任者,还是又一个依赖熟悉 API 来吸引 React 用户的框架。
Solid 以其性能、纯净和响应式自豪。但深入一看,你会发现……React。是的,还是那些 hooks,还是那样的组件结构,还是那些习惯。
Solid 站在巨人的肩膀上。在官方文档中,它提到了 React 和 Knockout。如果你以前用 React 的函数式组件和 hooks 编程——你会觉得几乎像回家一样。
Solid 并没有发明新东西。它是在优化旧的。它采用熟悉的模式,让它们更快、更简单、更透明。没有虚拟 DOM。没有组件重新渲染。没有多余的魔法。
这就引出了核心问题: 真正的区别是什么? 如果"一切都已经可以用了",Solid 为什么还要出现?
很多人讨厌 React 的 reconciliation 和虚拟 DOM。
咕咕咕,慢。咕咕咕。
让我们回忆一下 React 为什么要这样做。它的出现是为了简化对真实 DOM 的操作。核心思想如下:
- 浏览器中的 DOM 很慢且脆弱。
- 开发者直接管理节点、diff 和属性很不方便。
- React 提供了这样的方式:只需描述在某些数据下 UI 应该是什么样子。
- React 会在底层比较新旧树,只更新必要的部分(这就是 reconciliation)。
然后 Solid 出现了,我有了基于信号的依赖图,只会更新真正依赖信号的应用部分。
Solid 不会重新创建组件,也不会构建新的虚拟 DOM。它只会更新实际依赖于变更状态的 DOM 部分。
条件渲染如下所示:
<Show when={loggedIn()} fallback={<p>未登录</p>}>
<p>欢迎回来!</p>
</Show>
或者这样:
const output = createMemo(() => {
return condition() ? <A /> : <B />;
});
但这样是行不通的:
const [count, setCount] = createSignal(0);
const even = count() % 2 === 0;
return (
<>
<button onClick={() => setCount(count() + 1)}>Inc</button>
{even ? <p>偶数</p> : <p>奇数</p>}
</>
);
even
只在初始化时计算一次,因为 count()
被写在了 JSX 外。
Solid 不会追踪 count
作为该表达式的依赖。
改变 count()
不会导致 even
重新计算,因为 even
不是响应式的。
如果你想在函数体内做条件渲染,请这样写:
return <>{count() % 2 === 0 ? <Even /> : <Odd />}</>;
我还记得在使用代理对象时也会丢失响应性。例如在 mobX 中,如果你在非响应式上下文中不规律地取出响应式对象的值,也会丢失响应性。
const state = reactive({ count: 0 });
const even = state.count % 2 === 0;
watchEffect(() => {
console.log(even); // ❌ 不会生效,even 不是响应式的
});
我们发现,使用代理和信号都可能丢失响应性。
那为什么不用 Proxy?为什么要用信号?Signal 到底是什么?我太懒了……这里直接贴一下 readSignal
函数的代码片段。
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;
}
主要的魔法在于观察者列表——组件或 effect 成为订阅者。
使用时会踩坑吗?就像上面丢失上下文的例子一样。
是的,会。
尤其是当你的目标用户是 React 开发者大军时。
React 的一切都是为了最大程度地简化和降低门槛。还记得 hooks 刚出来时是怎么宣传的吗?还有函数式组件。最初他们告诉我们:用 class 组件很难?我们让一切变得更简单。
唉,也许这些都是想象出来的问题。
最明显的一点当然是,如果你用 solid 替代 react,你需要更多的热情。毕竟 react 下的某些包在 solid 生态中可能没有对应的替代品。你得自己造轮子。你需要吗?自己决定吧。也许明天一切都会变。
这里本该有个关于 PHP 的笑话。