SolidJS 的两个坑

创建于 4/21/2026

组件等同于函数调用吗;逻辑运算符的特殊处理

在所有框架中,SolidJS 的理念我最喜欢。由于其概念很少,所以有坑的地方并不多。但依然是有的,不可不察。

本文代码在这里

组件等同于函数调用吗?

SolidJS 常常宣传其 Signal 和无虚拟 DOM,但最令我惊喜的是其 JSX。

SolidJS 的 JSX 编译后,是一棵非常简单的树。原生元素就是一个 DOM 元素,响应式就是直接操作 DOM;纯文本、数字等原始值直接保持原样;多个元素并列、文本插值之类,就是个数组;响应式的插值,就是个返回 JSX 的函数;自定义组件,几乎就是直接调用函数。什么 h 函数,不认识!

上面说的是创建 JSX。而把 JSX 转为真实 DOM,主要也只有两处:把 JSX 插入做 DOM 的子元素时、render 函数最终渲染时。后者同样是为了将 JSX 插到某个元素中。

不过,看着简单的东西,更要重视其中细节。自定义组件几乎就是调用函数,那差别在哪里?JSX 中,<A />{A({})} 是否相同?

查看编译输出可知,<A /> 会被编译成 createComponent(A, {}){A({})} 的编译结果则等价于 createMemo(() => A({}))。后者加一层记忆化,JSX 插值都会这么做,没啥影响。前者就有点意思了。

你可能会想,createComponent 不就是 h 改个名字嘛,肯定有一堆复杂逻辑。我也是这么想的,直到看了源码发现:如果不启用水合或调试模式,则 createComponent(Comp, props) 会直接返回 untrack(() => Comp(props || {}))!没错,除了调用函数外,仅仅多了一层 untrack

可以得出结论:<A />{A({})} 编译后里面都是直接调用函数,区别只在于外面加的一层是 createMemo 还是 untrack

不过,untrack 有什么用呢?能否找出一段代码,让二者行为不同?

我试了试,还真可以。给组件加个响应式参数,在组件初始化时读取参数以触发响应式,这样一来,每次更新响应式值时,直接调用组件函数的就会重新运行组件函数导致多次初始化,而使用 JSX 组件的就不会重新运行。虽然初始化时读取响应式参数不是好实践,但这种做法毕竟难以避免。所以直接使用组件就好,没必要调用函数。

还有个常见的场景是 render 处。Preact 文档明确说明此处不能直接调用组件函数而必须用 JSX,但 SolidJS 则不关心这个,因为那里有没有 untrack 无关紧要。大可以直接把组件本身传进去,point free 风格。

逻辑运算符的特殊处理

让 SolidJS 按条件显示元素的标准方法是 ShowSwitch 组件,而 React 等框架则偏好直接使用 &&?: 等逻辑运算符。我常常偷懒而直接使用了后者,看效果也正常。

但进一步想想,不对呀!以 s() > 0 && <A /> 为例,如果 s 变化了,这个表达式理论上会响应式地重新求值,也就会重复调用 A 并导致多次初始化。本该有 bug 的,怎么没有呢?

给表达式包上一层不影响求值的东西(例如 [expr][0]),发现确实出现了应有的 bug。查看编译后代码,如果插值表达式是与表达式或三元表达式(或表达式似乎没有处理),则左侧的条件会转为布尔值并记忆化(其实源码中的逻辑比这复杂,但大概就是这样)。于是,如果左侧保持真值不变,响应就不会传递到逻辑运算符中。

这个比 ShowSwitch 写起来方便得多,并且可以放在属性中,很好用。但要保持警觉,它是特殊处理的语法糖,不可组合,嵌套或者抽成函数就失效了。