本节摘要:React 里条件与列表就是普通表达式——三元、逻辑与、map 加 key;Solid 把它们升级为专用控制流组件
Show、Switch、For、Index。这不是语法糖偏好,而是必需品:组件只跑一次,写在表达式里的列表根本不会随数据更新。本节逐个对位,重点拆解For的引用跟踪与节点搬运策略。
别以为三元表达式和列表 map 能原样搬进 Solid——条件渲染还能用(值会变,表达式会重算),列表却会无声地坏掉。原因回到本章的主题:JSX 表达式里的 {list.map(...)} 在组件执行时求值一次,产出当时的节点集合;之后 list 再怎么变,这段代码都不会重跑——它不是绑定,只是历史。React 里 map 能工作,靠的是组件重渲染每次重新执行它;Solid 删掉了重渲染,就必须有人接管"数据集合到节点集合"的持续对齐工作。接管者就是 For。
条件类表达式在 Solid 里仍然可用——{ok() && <Tip/>} 是合法绑定,ok() 变化时分支会插拔。但三元嵌套两三层后可读性崩塌,且部分写法(逻辑与右侧的函数调用组件)容易漏调用。Show 是官方推荐形态:
import { Show, Switch, Match } from "solid-js"; // 单条件 + 备用分支 <Show when={user()} fallback={<LoginHint />}> <Profile user={user()} /> </Show> // 多分支:Switch 与 Match 对位 switch-case,比三元链清晰 <Switch fallback={<Guest />}> <Match when={role() === "admin"}><AdminPanel /></Match> <Match when={role() === "editor"}><EditorPanel /></Match> </Switch>
Show 还有一个 React 三元做不到的能力:when 接收真值判断时,分支内通过回调拿到收窄后的值。错误边界 ErrorBoundary 也是控制流家族成员,包住易错子树、fallback 渲染兜底界面,对位 React 的同名组件,用法几乎平移。
先看正确写法与它的机制:
import { For, Index } from "solid-js"; <For each={todos()} fallback={<Empty />}> {item => <TodoRow item={item} />} </For>
For 的对齐策略可以概括为"按引用认人":它维护着数据条目引用到 DOM 节点的映射,数据变化时做引用级比较——新数组里老条目还在,对应节点原封不动;顺序变了,节点被搬运(移动既有节点,不重建);条目消失,节点摘除、订阅回收;新条目出现,才创建新节点。React 的 key 机制目标相同,但工作在虚拟 DOM 对账层:key 帮协调器认出"这是同一个元素",然后比对属性差异生成补丁。两者最终效果接近,路径不同——For 在数据层就把对齐做完了,根本不产生"需要 diff 的描述"。
React 里 key 的经典坑——用数组索引当 key 导致条目状态错乱——在 For 里天然不存在,因为它根本不用索引认人。反过来,如果你的语义就是"按位置跟踪"(比如固定长度的仪表盘格子,位置即身份),Index 是正确工具:它按索引跟踪,条目值变化时只更新该位置的绑定,节点从不搬家。

| 场景 | React 写法 | Solid 写法 | 关键差异 |
|---|---|---|---|
| 单条件 | ok && <Tip/> 或三元 |
Show when fallback |
fallback 显式,回调可收窄真值 |
| 多分支 | 三元链 | Switch + Match |
分支是组件不是表达式,可读性高 |
| 列表 | map + key |
For |
引用跟踪,节点搬运,无 key 坑 |
| 按位置列表 | key={index}(反模式) |
Index |
位置即身份的正当场景 |
| 错误边界 | ErrorBoundary 类或库 |
ErrorBoundary 组件 |
内置,包住子树即生效 |
| 传送门 | createPortal |
Portal 组件 |
JSX 书写,宿主默认 body |
⚠️
For回调里的条目是按引用跟踪的值。若条目来自 store,更新条目内部字段时直接访问字段路径;把条目对象解构再用,字段级更新会失去订阅。需要条目级细粒度又怕踩坑时,最稳的组合是 store 管数据加 For 管列表,字段访问保持路径形式。
控制流家族还有两个成员补全图景。Portal 解决"渲染到组件树之外":弹层、通知、全屏遮罩要挂到文档根,React 用 createPortal(children, container),Solid 用 <Portal> 组件包住内容,宿主与挂载时机由属性指定,组件销毁时自动搬除——又是作用域回收语义在替你兜底。错误边界与 Suspense 的组合拳在控制流里同样是一等公民:外层 ErrorBoundary、内层 Suspense、数据由资源驱动,这套嵌套与 React 时代的心智一致,只是数据侧换成了 5.2 的资源。
拿一个典型 React 页面做改写示范,五个动作覆盖九成场景:
// 改写前(React) {loading && <Spin />} {error ? <Err msg={error} /> : data ? <List items={data} /> : <Empty />} {data.map(item => <Row key={item.id} item={item} />)} // 改写后(Solid) <Show when={loading()}><Spin /></Show> <Switch fallback={<Empty />}> <Match when={error()}><Err msg={error()} /></Match> <Match when={data()}><List items={data()} /></Match> </Switch> <For each={data()}>{item => <Row item={item} />}</For>
逐行对照可见,改写不是翻译而是简化:loading、error、data 三态的三段式嵌套被 Switch 的分支语义显式表达;key={item.id} 整个消失——For 按引用跟踪,id 字段从"对账凭证"降级为纯业务数据。改写清单五动作:条件包 Show、多路换 Switch、列表换 For、按位换 Index、弹层进 Portal。
至此渲染机制讲完。下一章从机制回到应用层:状态共享、数据资源与设计模式的迁移。