Chapter 05

Store 与复杂状态

createStore 提供嵌套响应式对象,produce 支持可变写法,reconcile 高效同步列表

1. Signal vs Store:如何选择

SolidJS 提供两种响应式数据容器,各有适用场景:

特性createSignalcreateStore
数据类型任意原始值/对象嵌套对象/数组
嵌套响应需要手动展开自动深层追踪
更新方式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 代理会追踪每个具体属性的访问,而不是整个对象。这意味着:

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。