You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SolidJS组件中用Immer的produce处理嵌套状态

SolidJS + Immer 嵌套子集修改方案(带撤销重做)

核心思路

不用Proxy,而是让createImmerSignal的createNested方法返回带修改能力的信号包装器——既可以直接读取子集数据,又能通过明确的路径/更新逻辑触发Immer对根对象的修改,同时保留类型安全与撤销重做能力。

实现步骤

1. 基础版:手动指定反向更新逻辑

先实现最直观的版本,通过选择器和反向更新函数明确子集与根对象的关联:

import { produce } from "immer";
import { createSignal, Setter } from "solid-js";

// 定义ImmerSignal类型,包含嵌套创建方法
type ImmerSignal<T> = [
  get: () => T,
  set: Setter<T>,
  createNested: <K>(
    selector: (root: T) => K,
    updater: (root: T, newValue: K) => void
  ) => [get: () => K, set: Setter<K>]
];

function createImmerSignal<T>(initialValue: T): ImmerSignal<T> {
  const [state, setState] = createSignal(initialValue);
  const undoStack: T[] = [];
  const redoStack: T[] = [];

  // 根状态更新方法,集成Immer与历史栈
  const set: Setter<T> = (updater) => {
    undoStack.push(state());
    redoStack.length = 0;
    setState((prev) =>
      produce(prev, (draft) => {
        typeof updater === "function" ? updater(draft) : Object.assign(draft, updater);
      })
    );
  };

  // 创建嵌套信号的核心方法
  const createNested = <K>(
    selector: (root: T) => K,
    updater: (root: T, newValue: K) => void
  ): [() => K, Setter<K>] => {
    // 读取子集:直接通过选择器从根状态获取
    const getNested = () => selector(state());
    // 修改子集:触发根状态更新,用Immer处理嵌套修改
    const setNested: Setter<K> = (newValue) => {
      set((root) => {
        produce(root, (draft) => {
          if (typeof newValue === "function") {
            // 支持函数式更新,直接修改子集实例
            newValue(selector(draft));
          } else {
            // 替换子集实例
            updater(draft, newValue);
          }
        });
      });
    };
    return [getNested, setNested];
  };

  return [state, set, createNested];
}

2. 类型安全的使用示例

假设你的数据结构是嵌套类实例:

class Developer {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

class Company {
  name: string;
  developers: Developer[];
  constructor(name: string, developers: Developer[]) {
    this.name = name;
    this.developers = developers;
  }
}

// 初始化根信号
const initialCompany = new Company("Acme", [new Developer("Alice", 30)]);
const [company, setCompany, createNested] = createImmerSignal(initialCompany);

// 创建指向第一个开发者的嵌套信号
const [firstDev, setFirstDev] = createNested(
  (c) => c.developers[0],
  (c, newDev) => { c.developers[0] = newDev; }
);

// 子组件中直接使用
// 读取数据
console.log(firstDev().name); // 输出 "Alice"
// 修改数据(替换实例)
setFirstDev(new Developer("Bob", 28));
// 函数式更新(修改实例属性)
setFirstDev((prev) => { prev.age += 1; });

3. 优化版:自动路径解析(避免手动写updater)

如果不想每次都写反向更新函数,可以用字符串路径代替选择器,通过辅助函数自动解析路径修改根对象:

// 辅助工具:根据路径读取/设置对象属性
function getPath<T>(obj: T, path: string): any {
  return path.split(".").reduce((acc, key) => acc[key], obj);
}

function setPath<T>(obj: T, path: string, value: any): void {
  const keys = path.split(".");
  const lastKey = keys.pop()!;
  const parent = keys.reduce((acc, key) => acc[key], obj);
  parent[lastKey] = value;
}

// 修改createNested方法,支持路径字符串
const createNested = <K>(path: string): [() => K, Setter<K>] => {
  const getNested = () => getPath(state(), path) as K;
  const setNested: Setter<K> = (newValue) => {
    set((root) => produce(root, (draft) => {
      if (typeof newValue === "function") {
        newValue(getPath(draft, path));
      } else {
        setPath(draft, path, newValue);
      }
    }));
  };
  return [getNested, setNested];
};

// 使用方式更简洁,配合TS类型工具可实现路径校验
const [firstDev, setFirstDev] = createNested<Developer>("developers.0");

4. 集成撤销重做

所有修改都通过根信号的set方法触发,因此只需在createImmerSignal中添加历史栈管理:

// 在createImmerSignal中添加undo/redo方法
const undo = () => {
  if (undoStack.length === 0) return;
  const prevState = undoStack.pop()!;
  redoStack.push(state());
  setState(prevState);
};

const redo = () => {
  if (redoStack.length === 0) return;
  const nextState = redoStack.pop()!;
  undoStack.push(state());
  setState(nextState);
};

// 更新ImmerSignal类型
type ImmerSignal<T> = [
  get: () => T,
  set: Setter<T>,
  createNested: <K>(path: string) => [get: () => K, set: Setter<K>],
  undo: () => void,
  redo: () => void
];

// 返回时包含undo/redo
return [state, set, createNested, undo, redo];

方案优势

  • 性能高效:无需Proxy的隐式追踪,直接通过路径或更新函数修改,性能损耗极小
  • 类型安全:选择器或路径字符串可通过TS类型校验,确保子集存在且类型正确
  • 逻辑清晰:修改路径明确,调试时能直接定位到根对象的修改点
  • 兼容类实例:依托Immer的能力,完美支持嵌套类实例的修改

内容的提问来源于stack exchange,提问作者Basil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:35:57