Cover Image for SolidJS — 终极综述
[JavaScript][SolidJS][前端]
2025年5月25日

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 的笑话。

继续阅读

加入我们的社区