Zustand 第二章(状态处理)

状态处理

在上一章我们讲了,Zustand会合并第一层的state,但是如果深层次应该如何处理呢

来吧演示

首先创建一个葫芦娃,葫芦娃有七个娃,每个娃都有自己的状态,我们可以通过updateGourd来更新葫芦娃的状态,这样就实现了一个深层次的demo

ts 复制代码
import { create } from 'zustand'

interface User {
    gourd: {
        oneChild: string,
        twoChild: string,
        threeChild: string,
        fourChild: string,
        fiveChild: string,
        sixChild: string,
        sevenChild: string,
    },
    updateGourd: () => void
}
const useUserStore = create<User>(((set) => ({
    //创建葫芦娃
    gourd: {
        oneChild: '大娃',
        twoChild: '二娃',
        threeChild: '三娃',
        fourChild: '四娃',
        fiveChild: '五娃',
        sixChild: '六娃',
        sevenChild: '七娃',
    },
    updateGourd: () => set((state) => ({
        gourd: {
            //...state.gourd, 先不进行状态合并  // [!code highlight] 
            oneChild: '大娃-超进化',
        }
    }))
})))

export default useUserStore;

我们会发现如果不进行状态合并,其他的状态是会丢失的,所以深层次的状态处理需要进行状态合并,但是如果代码过多,每次都需要合并状态也挺烦的,所以我们可以通过immer中间件处理这个问题

使用immer中间件

安装

bash 复制代码
npm install immer

原始immer的用法

需要导出produce,然后它的第一个参数是原始值,第二个参数是一个回调函数,回调函数中的参数是draft,也就是原始值的拷贝,然后我们就可以直接修改draft了,最后返回新的值

ts 复制代码
import { produce } from 'immer'

const data = {
  user: {
    name: '张三',
    age: 18
  }
}

const newData = produce(data, draft => {
  draft.user.age = 20
})

console.log(newData,data) 
//{ user: { name: '张三', age: 20 } } 
//{ user: { name: '张三', age: 18 } }

immerZustand中的使用方法

引入注意是从zustand/middleware/immer引入,而不是immer

ts 复制代码
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
  1. 首先从zustand中间件引入immer
  2. 然后注意结构create()(immer())这里是两个括号而不是放在create里面了
ts 复制代码
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface User {
    gourd: {
        oneChild: string,
        twoChild: string,
        threeChild: string,
        fourChild: string,
        fiveChild: string,
        sixChild: string,
        sevenChild: string,
    },
    updateGourd: () => void
}
//注意结构发生了变化!!!
const useUserStore = create<User>()(immer(((set) => ({
    //创建葫芦娃
    gourd: {
        oneChild: '大娃',
        twoChild: '二娃',
        threeChild: '三娃',
        fourChild: '四娃',
        fiveChild: '五娃',
        sixChild: '六娃',
        sevenChild: '七娃',
    },
    updateGourd: () => set((state) => {
        state.gourd.oneChild = '大娃-超进化' //这儿不需要状态合并了需要修改什么值直接改就行了
        state.gourd.twoChild = '二娃-谁来了'
        state.gourd.threeChild = '三娃-我来了'
    })
}))))

export default useUserStore;

immer原理剖析

immer.js 通过 Proxy 代理对象的所有操作,实现不可变数据的更新。当对数据进行修改时,immer 会创建一个被修改对象的副本,并在副本上进行修改,最后返回修改后的新对象,而原始对象保持不变。这种机制确保了数据的不可变性,同时提供了直观的修改方式。

immer 的核心原理基于以下两个概念:

  1. 写时复制 (Copy-on-Write)
  • 无修改时:直接返回原对象
  • 有修改时:创建新对象
  1. 惰性代理 (Lazy Proxy)
  • 按需创建代理
  • 通过 Proxy 拦截操作
  • 延迟代理创建

工作流程

graph TD A[调用 produce] --> B[创建代理 draft] --> C[执行 recipe 修改 draft] --> D[是否有修改!] D-- 是 --> E[创建新对象...base + ...modified] D-- 否 --> F[直接返回 base]

简化实现

ts 复制代码
type Draft<T> = {
  -readonly [P in keyof T]: T[P];
};

function produce<T>(base: T, recipe: (draft: Draft<T>) => void): T {
  // 用于存储修改过的对象
  const modified: Record<string, any> = {};
  
  const handler = {
    get(target: any, prop: string) {
      // 如果这个对象已经被修改过,返回修改后的对象
      if (prop in modified) {
        return modified[prop];
      }
      
      // 如果访问的是对象,则递归创建代理
      if (typeof target[prop] === 'object' && target[prop] !== null) {
        return new Proxy(target[prop], handler);
      }
      return target[prop];
    },
    set(target: any, prop: string, value: any) {
      // 记录修改
      modified[prop] = value;
      return true;
    }
  };

  // 创建代理对象
  const proxy = new Proxy(base, handler);
  
  // 执行修改函数
  recipe(proxy);
  
  // 如果没有修改,直接返回原对象
  if (Object.keys(modified).length === 0) {
    return base;
  }
  
  // 创建新对象,只复制修改过的属性
  return {
    ...base,
    ...modified
  };
}

// 使用示例
const state = {
  user: {
    name: '张三',
    age: 25
  }
};

const newState = produce(state, draft => {
  draft.user.name = '李四';
  draft.user.age = 26;
});

console.log(state);     // { user: { name: '张三', age: 25 } }
console.log(newState);  // { user: { name: '李四', age: 26 } }

这儿只是简单实现,没有考虑数组的情况和深层次的代理,只实现了其核心思想

相关推荐
@大迁世界8 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路17 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug20 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213822 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中44 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
前端工作日常1 小时前
我学习到的AG-UI的概念
前端