1. Signal vs Store:如何选择
SolidJS 提供两种响应式数据容器,各有适用场景:
| 特性 | createSignal | createStore |
|---|---|---|
| 数据类型 | 任意原始值/对象 | 嵌套对象/数组 |
| 嵌套响应 | 需要手动展开 | 自动深层追踪 |
| 更新方式 | setSignal(newValue) | setStore 路径式更新 |
| 性能 | 最简单场景最优 | 深层结构更优 |
| 推荐场景 | 计数器、布尔值、简单对象 | 表单、用户资料、列表数据 |
2. createStore — 嵌套响应式
createStore 返回 [store, setStore]。store 是一个深层响应式代理,访问任意嵌套属性时都会自动追踪依赖。
import { createStore } from "solid-js/store";
interface UserState {
name: string;
age: number;
address: { city: string; zip: string; };
tags: string[];
}
const [user, setUser] = createStore<UserState>({
name: "Alice",
age: 25,
address: { city: "Beijing", zip: "100000" },
tags: ["developer", "solid"],
});
// 读取嵌套属性——自动追踪
console.log(user.address.city); // "Beijing"
// 更新方式一:路径式更新(最常用)
setUser("name", "Bob");
setUser("address", "city", "Shanghai");
setUser("tags", (tags) => [...tags, "new-tag"]);
// 更新方式二:对象合并
setUser({ name: "Charlie", age: 30 });
// 更新数组元素:通过索引
setUser("tags", 0, "updated-tag");
路径更新的高级用法
interface AppState {
todos: { id: number; text: string; done: boolean; }[];
}
const [state, setState] = createStore<AppState>({ todos: [] });
// 通过过滤函数选择要更新的数组元素
setState("todos", (todo) => todo.id === 1, "done", true);
// 等价于:将 id===1 的 todo 的 done 属性设为 true
// 批量更新所有 todo
setState("todos", {}, "done", false); // 全部设为未完成
3. produce — 可变风格更新
produce 允许用可变(mutable)的写法来更新 Store,背后使用 immer 风格的代理来生成不可变更新。适合复杂的嵌套更新逻辑:
import { createStore, produce } from "solid-js/store";
const [state, setState] = createStore({
todos: [
{ id: 1, text: "任务1", done: false, priority: 1 },
{ id: 2, text: "任务2", done: false, priority: 2 },
]
});
// 不用 produce:路径式更新(适合简单场景)
setState("todos", 0, "done", true);
// 用 produce:可变写法(适合复杂场景)
setState(
produce((draft) => {
// draft 是可以直接修改的代理对象
const todo = draft.todos.find(t => t.id === 1);
if (todo) {
todo.done = true;
todo.priority = 0;
}
// 添加新项
draft.todos.push({ id: 3, text: "新任务", done: false, priority: 1 });
// 排序
draft.todos.sort((a, b) => a.priority - b.priority);
})
);
4. reconcile — 高效同步外部数据
reconcile 用于将外部数据(如 API 响应)智能地同步到 Store,只更新真正变化的属性,保留未变化部分的响应式连接:
import { createStore, reconcile } from "solid-js/store";
const [store, setStore] = createStore({ users: [] });
async function refreshUsers() {
const newUsers = await fetchUsers();
// 不用 reconcile:替换整个数组,所有订阅者都会重新执行
// setStore("users", newUsers); // 效率低
// 用 reconcile:只更新变化的部分
setStore("users", reconcile(newUsers, { key: "id" }));
// reconcile 通过 id 字段识别相同项,只更新变化的属性
}
// reconcile 选项
reconcile(newData, {
key: "id", // 用于识别相同项的字段(默认 "id")
merge: true, // true: 合并更新; false: 替换(默认 false)
});
5. 深层属性追踪
Store 代理会追踪每个具体属性的访问,而不是整个对象。这意味着:
- 只访问
user.name的 Effect,只在 name 变化时重新执行 - 即使
user.age变化,该 Effect 也不会重新执行 - 这就是"细粒度"响应式的核心优势
const [user, setUser] = createStore({ name: "Alice", age: 25, score: 100 });
// 只追踪 name
createEffect(() => console.log("name effect:", user.name));
// 只追踪 score
createEffect(() => console.log("score effect:", user.score));
setUser("age", 26); // 两个 Effect 都不执行!
setUser("name", "Bob"); // 只有 name effect 执行
setUser("score", 200); // 只有 score effect 执行
6. 实战:Todo 应用完整状态管理
import { createStore, produce } from "solid-js/store";
import { createMemo, createSignal } from "solid-js";
import { For, Show } from "solid-js";
interface Todo {
id: number;
text: string;
done: boolean;
}
function TodoApp() {
const [store, setStore] = createStore({ todos: [] as Todo[] });
const [input, setInput] = createSignal("");
const [filter, setFilter] = createSignal<"all"|"active"|"done">("all");
let nextId = 1;
const filtered = createMemo(() => {
const f = filter();
if (f === "active") return store.todos.filter(t => !t.done);
if (f === "done") return store.todos.filter(t => t.done);
return store.todos;
});
const addTodo = () => {
const text = input().trim();
if (!text) return;
setStore("todos", t => [...t, { id: nextId++, text, done: false }]);
setInput("");
};
const toggleTodo = (id: number) => {
setStore("todos", t => t.id === id, "done", d => !d);
};
const deleteTodo = (id: number) => {
setStore("todos", t => t.filter(todo => todo.id !== id));
};
const clearDone = () => {
setStore("todos", t => t.filter(todo => !todo.done));
};
return (
<div class="todo-app">
<h1>Todo List</h1>
<div class="input-row">
<input
value={input()}
onInput={(e) => setInput(e.currentTarget.value)}
onKeyDown={(e) => e.key === "Enter" && addTodo()}
placeholder="新增任务..."
/>
<button onClick={addTodo}>添加</button>
</div>
<For each={filtered()} fallback={<p>暂无任务</p>}>
{(todo) => (
<div class={`todo-item ${todo.done ? "done" : ""}`}>
<input type="checkbox" checked={todo.done} onChange={() => toggleTodo(todo.id)} />
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</div>
)}
</For>
<div class="footer">
<button onClick={() => setFilter("all")}>全部</button>
<button onClick={() => setFilter("active")}>进行中</button>
<button onClick={() => setFilter("done")}>已完成</button>
<button onClick={clearDone}>清除已完成</button>
</div>
</div>
);
}
本章小结:createStore 提供深层响应式对象,路径式 setter 精确更新嵌套属性,produce 支持可变写法处理复杂逻辑,reconcile 高效同步外部数据。选择:简单值用 Signal,嵌套对象/列表用 Store。