利用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里面是有发布订阅模式存在的。后面去手写了一遍核心代码

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho5 小时前
【TypeScript】知识点梳理(三)
前端·typescript