要点
本篇是 SolidJS 及其配套生态的条目式速查,按用途分六组:响应式原语(solid-js)、控制流组件、组件与 props、状态库(solid-js/store)、路由与数据(@solidjs/router)、SolidStart 工程;文末有全量速查表,可只搜表定位条目。版本基线:solid-js 1.9.10、@solidjs/router 0.15.4、SolidStart 1.2.0(截至 2025-11-20 的稳定线),仅在此基线成立的特性单独标注。路由与数据的机制讲解不在本篇重复,见SolidStart 路由与数据;框架定位与响应式模型见开山篇SolidJS 入门。
响应式原语组(solid-js)
| API | 一句话用途 | 有什么坑 |
|---|---|---|
| createSignal(v) | 声明状态,返回 [取值函数, 写入函数] | 读值必须调用 count();把 count 当值传出就丢追踪,传 count 才保活性 |
| createMemo(fn) | 派生值,依赖变化自动重算并缓存 | 派生链里再写别的信号会造成级联更新,保持纯读 |
| createEffect(fn) | 副作用,自动追踪执行中读到的信号 | 能从别的值算出来的别放这里;那是 createMemo 的活 |
| createResource(fn) | 异步资源,集成 Suspense/ErrorBoundary | 读 data() 的组件必须在 Suspense 内,否则整树进入等待 |
| on(deps, fn) | 显式声明依赖的副作用 | 默认首次执行,不要首跑要传 { defer: true } |
| onMount(fn) | 首次渲染后执行一次 | 没有”依赖数组”概念——组件函数只跑一次 |
| onCleanup(fn) | 注册清理逻辑,作用域销毁时执行 | 计时器、订阅、事件监听都走它,没有 useEffect 式的 return 清理 |
| onError(fn) | 作用域级错误捕获 | 兜不住子树内已被 ErrorBoundary 处理的错误 |
| untrack(fn) | 读信号但不建立依赖 | effect 里只想”看一眼”大对象时用,防误触发 |
| batch(fn) | 合并多次写入为一次更新 | 逐条 set 引发多次更新时才需要 |
| createDeferred(v) | 延迟到空闲时计算的派生值 | 大计算让路给渲染的场景 |
| createContext / useContext | 跨层级共享数据 | context 值要保持响应性就传取值函数,传快照同 signal 的坑 |
createSignal:取值函数是心智根基
import { createSignal } from "solid-js";
const [count, setCount] = createSignal(0);
setCount(count() + 1); // 用当前值写
setCount(c => c + 1); // 基于前值写,批量场景更稳
一句话语义:声明”这个值变了,读它的地方要更新”。坑在传递:JSX 里 {count()} 是读值并建立订阅;value={count()} 传的是快照,value={count} 传的是取值函数——想让子组件里的变化触发更新,读取必须发生在渲染表达式里。
createResource:什么时候用它而不是 query
const [data, { refetch, mutate }] = createResource(() =>
fetch("/api/boards").then(r => r.json())
);
data.loading; data.error; refetch(); // 四件套:数据、加载态、错误、手动重取
一句话语义:把 Promise 变成带加载态的响应式资源。分工判断:单组件内部的一次性异步用 createResource;路由级、需要按 key 去重缓存的数据走 router 的 query(见路由组),后者才能在多组件间共享缓存并在 action 后自动刷新。
控制流组件组
| 组件 | 一句话用途 | 有什么坑 |
|---|---|---|
| 条件渲染,可带 fallback | 切换即销毁重建;keyed 开启后对象变化也重建 | |
| 列表渲染,按引用追踪每项 | 适合对象数组;重排时移动 DOM 节点而非重建 | |
| 列表渲染,按索引追踪 | 原始值数组必须用它;子项是取值函数要调用 | |
| / | 多分支条件渲染 | 命中第一个 when 为真的分支,注意分支顺序 |
| 动态组件或标签 | 换标签渲染场景,比三元堆叠清晰 | |
| 渲染到 DOM 其他位置 | 弹窗挂 body 用;事件仍走 Solid 上下文 | |
| 异步等待边界 | 资源读取要落在它内部才触发 fallback,边界越细首屏越早 | |
| 错误兜底边界 | fallback 用函数可拿到错误对象与重置回调 |
For 还是 Index:看数组元素是什么
<For each={todos()}>{(todo) => <li>{todo.title}</li>}</For>
<Index each={cells()}>{(cell, i) => <td>{cell()}</td>}</Index>
一句话语义:For 按引用追踪,对象数组重排时直接移动现成节点;Index 按索引追踪,元素本身被替换时也只更新那一格。坑:For 渲染字符串/数字数组时没有引用可追,改动一个元素是整列表重建——原始值数组换 Index。
组件与 props 组
| API | 一句话用途 | 有什么坑 |
|---|---|---|
| props 对象 | 属性访问带 getter,保持响应性 | 解构 props 是头号 bug 来源,开山篇已详述 |
| splitProps(props, keys) | 拆出本地属性与透传属性 | 拆出来的仍是响应式引用,不是退化成值 |
| mergeProps(…sources) | 合并多来源 props(含默认值) | 包装组件别用展开运算符合并,会丢响应性 |
| children(fn) | 访问 JSX 子元素 | children 是取值函数,要调用 children() 才是内容 |
| lazy(() => import()) | 代码分割的组件 | 要在 Suspense 下等待,否则报 hydration 错误 |
| createComponent(fn) | 创建组件的底层入口 | 业务代码不直接写,做包装库时才用到 |
splitProps:解构 props 的正牌替代品
function Card(props) {
const [local, rest] = splitProps(props, ["title"]);
return <li {...rest}>{local.title}</li>; // title 留下渲染,其余透传
}
一句话语义:把 props 拆成”本组件要用的”和”继续传下去的”,两边都保持响应性。坑:const { title, ...rest } = props 这种 React 习惯写法会同时弄死两边——title 变成快照,rest 丢掉 getter。
状态库组(solid-js/store)
| API | 一句话用途 | 有什么坑 |
|---|---|---|
| createStore(init) | 深层响应式状态对象 | 更新走 setState 路径语法,别整对象替换 |
| setState(path, v) | 路径级精确更新 | setState(“cards”, 0, “title”, v) 只动一个节点,订阅该节点的才更新 |
| produce(fn) | 类 immer 的变更写法 | 配 setState(“list”, produce(list => …)) 用,单独用没意义 |
| reconcile(data) | 整对象替换时的差量对齐 | 服务端全量数据进来时用它,避免全树重渲 |
| unwrap(store) | 取出无代理的原始对象 | 结果不再响应式,只用于序列化或比对 |
| createMutable(init) | 可直接改的响应式对象 | 与 signal 风格混用会难追踪,团队内二选一 |
createStore:路径更新是差量的来源
import { createStore, produce } from "solid-js/store";
const [state, setState] = createStore({ cards: [{ id: 1, title: "设计" }] });
setState("cards", 0, "title", "设计评审"); // 精确到一个字段
setState("cards", produce(l => l.push({ id: 2, title: "开发" }))); // 数组操作
一句话语义:把嵌套状态变成可按路径订阅的对象树。坑:写成 setState(state => ({ cards: [...state.cards] })) 整体替换,差量更新优势就没了——要么路径更新,要么配 reconcile。
路由与数据组(@solidjs/router 0.15.x)
useSearchParams:筛选状态进 URL 的标准写法
const [params, setParams] = useSearchParams();
setParams({ page: 2 }); // 写进 URL,可分享可后退
<a href={"?page=" + (Number(params.page) || 1) + 1}>下一页</a>
一句话语义:把查询串变成双向绑定的响应式状态。坑:params.page 是字符串或 undefined,参与运算前先转数字;组件卸载不需要清它——状态本来就在 URL 里。
SolidStart 工程组
| 条目 | 一句话用途 | 有什么坑 |
|---|---|---|
| app.config.ts | defineConfig({ ssr, server }) | ssr: false 是 SPA 模式;server.preset 是 Nitro 部署目标(node-server/vercel/netlify 等) |
| vinxi dev | 开发服务器(npm run dev) | 默认 3000 端口 |
| vinxi build / start | 构建与生产启动 | node-server preset 产物在 .output/server/index.mjs |
| “use server” | 标记函数只在服务端执行 | 漏写则整段进客户端产物,数据库连接与密钥一起泄漏 |
| APIEvent | API 端点入参 | { request, params, fetch },来自 @solidjs/start/server |
| GET/POST 导出 | 路由文件导出方法名即端点 | 与页面同路径时 API 优先,GET 不返回值回落页面 |
| isServer(solid-js/web) | 运行环境布尔值 | 替代 typeof window !== “undefined” 式判断 |
| @solidjs/meta | Title/Meta 文档头管理 | 配 MetaProvider 包住根布局才生效 |
| mount(@solidjs/start/client) | 客户端挂载入口 | 写在 src/index.tsx,不要在这里塞业务逻辑 |
全量速查表
| 条目 | 一句话用途 | 有什么坑 |
|---|---|---|
| createSignal | 状态 | 读值要调用;传取值函数不传快照 |
| createMemo | 派生缓存 | 能算出来的别放 effect |
| createEffect | 副作用 | 清理用 onCleanup |
| createResource | 异步资源 | 必须在 Suspense 内读 |
| onMount / onCleanup | 首跑 / 清理 | 组件只跑一次,没有依赖数组 |
| untrack / batch | 免追踪读 / 合并写入 | 防误触发与抖动 |
| Show / Switch-Match | 条件渲染 | 切换即销毁重建 |
| For / Index | 列表渲染 | 对象数组 For,原始值数组 Index |
| Suspense / ErrorBoundary | 等待与错误边界 | 边界粒度决定首屏与兜底范围 |
| props / splitProps | 组件入参 | 解构丢响应性,拆分用 splitProps |
| createStore | 深层状态树 | 路径更新保差量 |
| reconcile / produce | 差量对齐 / immer 式写法 | 整对象替换必配 reconcile |
| A / useNavigate | 链接与编程导航 | 裸 a 整页刷新 |
| useParams / useLocation | 读路由状态 | 响应式对象,读即订阅 |
| useSearchParams | 查询串读写 | 值是字符串,先转数字 |
| query(cache 弃用) | 去重缓存读取 | key 稳定唯一,同参共享结果 |
| createAsync | 组件内消费 query | fetcher 同步读的信号是依赖 |
| action / useSubmission | 写操作与回执 | 成功后自动 revalidate 本页 query |
| redirect / reload | 跳转 / 手动重取 | redirect 要 throw |
| app.config.ts | ssr 与 Nitro preset | ssr:false 即 SPA 模式 |
| “use server” | 服务端专属函数 | 漏写就是密钥泄漏 |
| APIEvent / 方法名导出 | API 端点 | 同路径 API 优先 |
延伸阅读
文件路由与数据 API 的机制讲解见SolidStart 路由与数据;把这些条目组合成完整应用的过程见实战篇用 SolidJS 做一个看板小应用(2025-12-04 发布);按阶段串起本系列与前置知识的路径见SolidJS 学习路线(2025-12-11 发布)。ESM 模块机制不熟的,先补站内Node.js 模块与 ESM;异步与 Promise 基础见Node.js 异步与 Promise。
版本核实(以官方文档为准):截至本文(2025-11-20)稳定线为 solid-js 1.9.10、@solidjs/router 0.15.4、SolidStart 1.2.0(均为 npm 实查版本);cache→query 更名发生于 router 0.15.0,cache 保留为弃用别名;action 成功后自动 revalidate 本页 query、API 路由方法名导出、APIEvent 结构、”use server” 指令均为官方文档描述;solid-js/store 与控制流组件为 1.x 长期稳定 API。安装命令不钉版本号,API 细节以 docs.solidjs.com 当前文档为准。