利用zustand的思想封装hook

背景

最近项目里面需要自定义hook,达到从外部控制表单的作用。为什么选择去用自定义hook?这个组件其实内部已经封装了,并且是一个单独的npm包,目前没有人去维护,不仅可读性❌,以及暴露出来的方法也实在满足不了需求,性能也不优。

类似这样 然后我们核心就是给他传一个ref进去,它的内部主要利用useImperativeHandle去将内部的方法暴露出来

怎么去使用呢

我们就是从ref上面取,这样有一个不好的地方就是,我们开发者是没有感知的,比如我们不知道它暴露出来了哪些方法,并且我们每次调用都需要创建一个ref绑定,状态比较混乱。

hook改造

从上面的需求知道,我们要统一去管理一个form的状态,并且支持拓展,对开发者来说,有什么东西都是可以去感知的。

要做的其实是比较简单的,我们支持开发者外部传给我们一个form,values,如果没有的话,我们会在内部帮他初始化一份。这个form就是我们在用antd的form组件绑定的form, values就是我们form的表单项的值,这样就可以了。

但是这里有个问题,就是比如我们只想监听表单的某一项值,那我们该如何去呢,因为我们这里onValuesChange只要表单项变化了,就会重新赋值values,而state的变化,又会触发组件的重新渲染,所以我们想到了selector,我们可以类似zustand的selector一样,只去监听我们想要的那一项,我们先来看看不用selector的效果

我这里了打印的是subjectName,但是表单其他项变化,也会导致重复渲染,会有性能问题。我们就可以用这个selector函数来处理。

我们在监听某一项的时候,只需要传入一个selector函数即可,和zustand的selector很像

我们这里的Object.is是浅比较,在zustand源码里面,它的setState()其实也是用的浅比较,主要是性能问题

那深比较是如何做到的呢

那么对于深比较怎么做呢,zustand其实是利用了immerjs的特性。在zustand众多中间件中是有对immer中间件的支持。

Immer.js采用了一种称为结构共享的策略进行深层次的比较。这种策略的主要目的是在保留旧状态的同时创建一个新状态,并且在这个新状态中只更改必要的部分。

当使用Immer进行状态更新时,它创建了当前状态的一个draft(草稿) 。可以自由地修改这个draft,而不会影响原始状态。完成修改后,Immer会比较draft和原始状态,找出差异,并创建一个新的状态,这个新的状态会保留原始状态中没有改变的部分(结构共享),并包含你于draft中做出的修改。

这种方式有两个主要的优点:

  1. 性能:因为新的状态保留了旧状态中没有改变的部份,因此无需复制整个对象,这比深复制对象并修改差异,或者遍历整个对象进行比较要高效得多。
  2. 简化了不可变数据的使用:因为你可以直接修改draft,这就像在操作普通的JavaScript对象一样,不需要采用复杂的方式(如解构、展开运算符、Object.assign等)来更新对象和数组。

工作原理(基础)

Immer以当前状态和演绎函数(也称为producer)创建下一个状态。执行producer函数时,将传递一个"草稿(draft)"状态作为参数,而不是实际的状态。你可以对这个草稿状态进行更改,并且更改是"自由"的,就像直接改变普通的JS对象一样。当producer函数执行完毕时,Immer会比较草稿与当前状态的不同,并返回一个新的状态,这个新状态只修改了那些参与更改的部分。

工作原理(深层次)

深入底层来看,Immer使用Proxy来创建每一个属性的代理,这些属性可以是对象、数组或其它任何被代理到的数据类型。只有在这些属性被真正改变时,Immer才会对这些属性进行拷贝和修改,在这之前都是直接引用原对象。这就是为什么Immer可以在修改大型对象和深层次数据时保持高效性能的原因。这种优化策略被称为"结构共享"。

js 复制代码
import { create } from 'zustand-vue'

// import { create } from 'zustand'

import { immer } from 'zustand/middleware/immer'

const useBeeStore = create(
  immer((set) => ({
    bees: 0,
    addBees: (by) =>
      set((state) => {
        state.bees += by
      }),
  }))
)

中间件的使用

在zustand中是可以使用多个中间件组合在一起使用,我们看看是怎么用的

js 复制代码
import { devtools, persist } from 'zustand/middleware'

const useFishStore = create(
  devtools(persist(
    (set, get) => ({
      fishes: 0,
      addAFish: () => set({ fishes: get().fishes + 1 }),
    }),
  ))
)

比如我们使用devtools和persist中间件,中间价本质是一个函数,那我们多个中间价使用是有执行顺序的,在zustand中,中间件执行的顺序是从最内层开始到最外层,这种机制在函数编程中被称为"组合 "或"管道"。最内侧的中间件是最先执行的。这就像该中间件被装入到其他中间件中,并且其执行的结果会作为输入传递到下一个中间件。这就是所谓的"中间件的组合"。

总结

我们通过自己去封装hook了解了zustand代码的一些思想,特别是selector的使用,虽然我们写的比较简陋,但是思想确实类似,不过在zustand里面是有发布订阅模式存在的。后面去手写了一遍核心代码

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax