zustand 源码解析
文章目录
- 实现原理
- create
- createStore 创建实例
- CreateStoreImpl 实现发布订阅
- createImpl 包装返回给用户调用的 hook
- useSyncExternalStoreWithSelector 订阅更新
- zustand 性能优化
- 自定义数据更新
- createWithEqualityFn
- createWithEqualityFnImpl 返回 hook
- useSyncExternalStoreWithSelector 自定义比较函数
- 使用 useShallow 浅比较
- useShallow
- shallow 浅比较实现
- 使用 immer
- 在组件外部数据订阅
- SSR 数据同步
实现原理
- 通过发布订阅管理数据状态,和客户端 useSyncExternalStoreWithSelector 解耦,支持 SSR
- 通过 useSyncExternalStoreWithSelector 结合上面步骤实现数据更新
- 通过 useSyncExternalStoreWithSelector 的自定义切片数据、自定义是否更新状态函数、浅比较数据实现性能优化
- 数据 set 时通过Object.is 比较是否变化
- 相比 === 的差异在 NAN、±0 的比较上
- 数据 set 时的合并通过 Object.assign 实现
create
export const create = (<T>(createState: StateCreator<T, [], []> | undefined) =>createState ? createImpl(createState) : createImpl) as Create
createStore 创建实例
export const createStore = ((createState) =>createState ? createStoreImpl(createState) : createStoreImpl) as CreateStore
CreateStoreImpl 实现发布订阅
- 通过闭包保存 state
- 通过发布订阅管理 state
- 数据的合并通过 Object.assign实现
- 这样实现的好处是可以在服务端使用,而不依赖于 useSyncExternalStoreWithSelector
const createStoreImpl: CreateStoreImpl = (createState) => {type TState = ReturnType<typeof createState>type Listener = (state: TState, prevState: TState) => voidlet state: TState// Set 去重const listeners: Set<Listener> = new Set()const setState: StoreApi<TState>['setState'] = (partial, replace) => {// 判断 set 时传入的是对象还是函数const nextState =typeof partial === 'function'? (partial as (state: TState) => TState)(state): partial// 判断数据是否变化if (!Object.is(nextState, state)) {const previousState = state// 是否直接 replace 替换 state,还是 merge 合并 statestate =replace ?? (typeof nextState !== 'object' || nextState === null)? (nextState as TState): Object.assign({}, state, nextState)// 数据变换后发布通知listeners.forEach((listener) => listener(state, previousState))}}// 直接获取保存的闭包 stateconst getState: StoreApi<TState>['getState'] = () => state// 添加订阅const subscribe: StoreApi<TState>['subscribe'] = (listener) => {listeners.add(listener)// Unsubscribereturn () => listeners.delete(listener)}// 取消所有订阅直接调用 Set 的 clearconst destroy: StoreApi<TState>['destroy'] = () => {if (import.meta.env?.MODE !== 'production') {console.warn('[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected.',)}listeners.clear()}const api = { setState, getState, subscribe, destroy }// createState 是用户传入的函数,传入的函数会拿到 setState、getState 和 api 方法state = createState(setState, getState, api)return api as any
}
createImpl 包装返回给用户调用的 hook
- 实际是先调用上面的 createStore 先为状态创建对应的发布订阅实例
- 再通过 useStore 将发布订阅和 useSyncExternalStoreWithSelector 联系起来进而可以发布订阅后通知 React 进行更新
const createImpl = <T>(createState: StateCreator<T, [], []>) => {if (import.meta.env?.MODE !== 'production' &&typeof createState !== 'function') {console.warn("[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.",)}// 管理状态的实例(发布订阅)const api =typeof createState === 'function' ? createStore(createState) : createState// 交给用户调用的 hook,通过 useStore 封装 useSyncExternalStoreWithSelectorconst useBoundStore: any = (selector?: any, equalityFn?: any) =>useStore(api, selector, equalityFn)// 添加额外 APIObject.assign(useBoundStore, api)return useBoundStore
}
useSyncExternalStoreWithSelector 订阅更新
- useSyncExternalStoreWithSelector 比 useSyncExternalStore 性能更好,可以定义想订阅的状态
// 通过 useSyncExternalStoreWithSelector hook 订阅外部存储,并返回你关心的数据部分
export function useStore<TState, StateSlice>(api: WithReact<StoreApi<TState>>,selector: (state: TState) => StateSlice = api.getState as any,equalityFn?: (a: StateSlice, b: StateSlice) => boolean,
) {if (import.meta.env?.MODE !== 'production' &&equalityFn &&!didWarnAboutEqualityFn) {console.warn("[DEPRECATED] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`. They can be imported from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937",)didWarnAboutEqualityFn = true}// 返回状态,此时发布订阅和 React 更新关联上了const slice = useSyncExternalStoreWithSelector(api.subscribe,api.getState,api.getServerState || api.getState,selector, // 选择外部存储中你关心的数据部分equalityFn,// 自定义是否重新渲染)useDebugValue(slice)return slice
}
zustand 性能优化
自定义数据更新
- zustand 支持传入一个自定义比较函数确定数据是否变化
- 实质是 useSyncExternalStoreWithSelector 支持传入比较函数
createWithEqualityFn
- 不适用默认的 create ,使用 createWithEqualityFn 并传入比较函数
export const createWithEqualityFn = (<T>(createState: StateCreator<T, [], []> | undefined,defaultEqualityFn?: <U>(a: U, b: U) => boolean,
) =>createState? createWithEqualityFnImpl(createState, defaultEqualityFn): createWithEqualityFnImpl) as CreateWithEqualityFn
createWithEqualityFnImpl 返回 hook
- 取消 Object.is 比较数据变化
const createWithEqualityFnImpl = <T>(createState: StateCreator<T, [], []>,defaultEqualityFn?: <U>(a: U, b: U) => boolean,
) => {// 创建发布订阅实例const api = createStore(createState)// 传入自定义比较函数,取消默认的 Object.is 比较const useBoundStoreWithEqualityFn: any = (selector?: any,equalityFn = defaultEqualityFn,) => useStoreWithEqualityFn(api, selector, equalityFn)Object.assign(useBoundStoreWithEqualityFn, api)// 返回用户调用的 hook return useBoundStoreWithEqualityFn
}
useSyncExternalStoreWithSelector 自定义比较函数
export function useStoreWithEqualityFn<S extends WithReact<StoreApi<unknown>>,U,
>(api: S,selector: (state: ExtractState<S>) => U,equalityFn?: (a: U, b: U) => boolean,
): Uexport function useStoreWithEqualityFn<TState, StateSlice>(api: WithReact<StoreApi<TState>>,selector: (state: TState) => StateSlice = api.getState as any,equalityFn?: (a: StateSlice, b: StateSlice) => boolean,
) {const slice = useSyncExternalStoreWithSelector(api.subscribe,api.getState,api.getServerState || api.getState,selector,equalityFn,)useDebugValue(slice)return slice
}
使用 useShallow 浅比较
- 可以通过 useShallow 浅比较提升性能
const { nuts, honey } = useBearStore(useShallow((state) => ({ nuts: state.nuts, honey: state.honey })),
)
- 浅比较的更新策略,可以看到相同属性的对象就算是新对象,比较时都会默认无变化提升性能
shallow(1, 1); // trueshallow('hello', 'hello'); // trueshallow({ a: 1 }, { a: 1 }); // trueshallow([1, 2, 3], [1, 2, 3]); // false
useShallow
- 保存 selector 结果,对应的结果会给到 useSyncExternalStoreWithSelector 的第四个参数,如果提供了第五个参数比较函数,则使用比较函数判断两次结果,默认第五个比较函数为 Object.is ,会跳过更新
const slice = useSyncExternalStoreWithSelector(api.subscribe,api.getState,api.getServerState || api.getState, selector, // 选择外部存储中你关心的数据部分,对应 useShallow 返回的函数equalityFn,// 自定义是否重新渲染,默认为Object.is)
const { useRef } = ReactExportsexport function useShallow<S, U>(selector: (state: S) => U): (state: S) => U {const prev = useRef<U>()// 通过闭包保存 selector 返回的结果,对应的结果会给到 useSyncExternalStoreWithSelector 的第四个参数 selector,如果返回的引用不变,不会出触发更新return (state) => {const next = selector(state)return shallow(prev.current, next)? (prev.current as U): (prev.current = next)}
}
shallow 浅比较实现
- 最主要的是判断当对象 key-value 一样时,不管是否是新建对象都会是认为一样的
export function shallow<T>(objA: T, objB: T) {if (Object.is(objA, objB)) {return true}if (typeof objA !== 'object' ||objA === null ||typeof objB !== 'object' ||objB === null) {return false}if (objA instanceof Map && objB instanceof Map) {if (objA.size !== objB.size) return falsefor (const [key, value] of objA) {if (!Object.is(value, objB.get(key))) {return false}}return true}if (objA instanceof Set && objB instanceof Set) {if (objA.size !== objB.size) return falsefor (const value of objA) {if (!objB.has(value)) {return false}}return true}const keysA = Object.keys(objA)if (keysA.length !== Object.keys(objB).length) {return false}for (let i = 0; i < keysA.length; i++) {if (!Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||!Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])) {return false}}return true
}/*** shallow(1, 1); // trueshallow('hello', 'hello'); // trueshallow({ a: 1 }, { a: 1 }); // trueshallow([1, 2, 3], [1, 2, 3]); // false*/
使用 immer
- immer 的中间件实现主要是拦截了发布订阅的 set 方法,通过immer去修改数据后,再调用原始 set 方法
const useBeeStore = create(immer((set) => ({bees: 0,addBees: (by) =>set((state) => {state.bees += by}),})),
)
const immerImpl: ImmerImpl = (initializer) => (set, get, store) => {type T = ReturnType<typeof initializer>// 拦截了 set 方法 store.setState = (updater, replace, ...a) => {// 通过 immer 修改数据const nextState = (typeof updater === 'function' ? produce(updater as any) : updater) as ((s: T) => T) | T | Partial<T>// 修改后再调用原始方法return set(nextState as any, replace, ...a)}return initializer(store.setState, get, store)
}export const immer = immerImpl as unknown as Immer
在组件外部数据订阅
- 同样是拦截了 subscribe 方法,添加订阅逻辑
const subscribeWithSelectorImpl: SubscribeWithSelectorImpl =(fn) => (set, get, api) => {type S = ReturnType<typeof fn>type Listener = (state: S, previousState: S) => voidconst origSubscribe = api.subscribe as (listener: Listener) => () => void// 拦截 subscribe api.subscribe = ((selector: any, optListener: any, options: any) => {let listener: Listener = selector // if no selectorif (optListener) {const equalityFn = options?.equalityFn || Object.is// TODO:这样后续更新后,无论如何都会更新,因为只比较第一次保存的状态let currentSlice = selector(api.getState())// 添加本次 listenerlistener = (state) => {const nextSlice = selector(state)// 只有当数据变化时才触发if (!equalityFn(currentSlice, nextSlice)) {const previousSlice = currentSliceoptListener((currentSlice = nextSlice), previousSlice)}}if (options?.fireImmediately) {optListener(currentSlice, currentSlice)}}return origSubscribe(listener)}) as anyconst initialState = fn(set, get, api)return initialState}
export const subscribeWithSelector =subscribeWithSelectorImpl as unknown as SubscribeWithSelector
SSR 数据同步
- 服务器返回的数据先存储在 localStroage 里,然后等组件 ready 后再水合 rehydrate 放进 zustand 中
相关文章:
zustand 源码解析
文章目录 实现原理createcreateStore 创建实例CreateStoreImpl 实现发布订阅createImpl 包装返回给用户调用的 hookuseSyncExternalStoreWithSelector 订阅更新zustand 性能优化自定义数据更新createWithEqualityFncreateWithEqualityFnImpl 返回 hookuseSyncExternalStoreWith…...
洛谷题单3-P1423 小玉在游泳-python-流程图重构
题目描述 小玉开心的在游泳,可是她很快难过的发现,自己的力气不够,游泳好累哦。已知小玉第一步能游 2 2 2 米,可是随着越来越累,力气越来越小,她接下来的每一步都只能游出上一步距离的 98 % 98\% 98%。现…...
ETPNav:基于演进拓扑规划的连续环境视觉语言导航模型
1、现有工作的缺陷: 最近,出现了一种基于模块化航路点的方法的新兴趋势,该方法将复杂任务分为航路点生成、子目标规划和导航控制: (1)在每个决策循环中,代理使用预训练的网络来预测附近的几个…...
Spring Cloud LoadBalancer负载均衡+算法切换
目录 介绍核心功能负载均衡启动两个支付服务订单模块引入依赖LoadBalanced 注解启动订单服务测试结果 负载均衡算法切换总结 介绍 Spring Cloud LoadBalancer 是 Spring Cloud 提供的客户端负载均衡解决方案,提供更现代化的 API 和更好的 Spring 生态系统集成。它支…...
游戏引擎学习第210天
回顾并为今天的工作做准备 今天我们,进行一些编码工作。这部分的编码内容对那些对代码架构感兴趣的人非常有帮助,我认为今天的编码内容会很有教育意义,尤其是在展示一些代码转化的过程中,希望大家能够从中获得一些启发。 接下来…...
XXL-JOB 分片广播模式深度解析:从原理到实战
前言 XXL-JOB 是一个轻量级的分布式任务调度平台,它以其简单易用、灵活扩展的特点受到了开发者的青睐。本文将深入探讨 XXL-JOB 的分片广播模式,包括其工作原理、实现方法、异常处理及监控告警策略,并通过 Java 代码示例和工作流程图来帮助大…...
UML类图综合实验三补档
1.使用简单工厂模式模拟女娲(Nvwa)造人(Person),如果传入参数“M”,则返回一个Man对象,如果传入参数“W”,则返回一个Woman对象,用Java语言实现该场景。现需要增加一个新的Robot类,如果传入参数“R”&#…...
WinForm真入门(11)——ComboBox控件详解
WinForm中 ComboBox 控件详解 ComboBox 是 WinForms 中一个集文本框与下拉列表于一体的控件,支持用户从预定义选项中选择或直接输入内容。以下从核心属性、事件、使用场景到高级技巧的全面解析: 一、ComboBox 核心属性 属性说明示例Items下拉…...
DeepSeek底层揭秘——《推理时Scaling方法》技术对比浅析
4月初,DeepSeek 提交到 arXiv 上的最新论文正在 AI 社区逐渐升温。 笔者尝试对比了“关于推理时Scaling”与现有技术,粗浅分析如下: 与LoRA的对比 区别: 应用场景:LoRA是一种参数高效微调方法,主要用于在…...
Android Coli 3 ImageView load two suit Bitmap thumb and formal,Kotlin(四)
Android Coli 3 ImageView load two suit Bitmap thumb and formal,Kotlin(四) 对 Android Coli 3 ImageView load two suit Bitmap thumb and formal,Kotlin(三)-CSDN博客 进行完善,注意完善 …...
Adam优化器研究综述
摘要 Adam优化器(Adaptive Moment Estimation)是一种广泛应用于深度学习的优化算法,通过自适应学习率加速梯度下降过程。本文从Adam的定义、算法原理、优势与局限性、应用场景及变体等方面进行调研,结合学术文献和实践经验&#x…...
在 macOS 上连接 PostgreSQL 数据库(pgAdmin、DBeaver)
在 macOS 上连接 PostgreSQL 数据库 pgAdmin 官方提供的图形化管理工具,支持 macOS。 下载地址:https://www.pgadmin.org/ pgAdmin 4 是对 pgAdmin 的完全重写,使用 Python、ReactJs 和 Javascript 构建。一个用 Electron 编写的桌面运行时…...
使用文本翻译API打破语言障碍
一、引言 在当今全球化的商业环境中,企业面临着前所未有的语言挑战。无论是出口商品、引进技术,还是与国际客户进行交流,语言障碍始终是一个亟待解决的问题。文本翻译API作为一款高效、稳定的工具,支持多种语言的翻译,…...
UniappX动态引入在线字体图标,不兼容css时可用。
优缺点 优点:不需要占用本地存储,可直接在线同步库图标,不用再手动引入ttf文件,不用手动添加键值对对应表。 缺点:受网速影响,字体库cdn路径可能会更改,ios端首次加载,可能会无图标…...
2018年真题
数学基础 一、 (共4分)用逻辑符号表达下列语句(论域为包含一切事物的集合) 1、(2分)集合A的任一元素的元素都是A的元素 经过对图片文字的识别与逻辑分析,结果如下: 符号定义&…...
Efficient Burst Raw Denoising:稳定噪声方差和分频率降噪
Efficient Burst Raw Denoising with Stabilization and Multi-Frequency Denoising Network Burst Raw Denoising必要性Burst Raw Image Denoising流程Main Contributions具体方法介绍集成noise priorCMOS sensor 噪声建模噪声变换(Variance stabilization…...
Ansible的使用2
#### 一、Ansible变量 ##### facts变量 > facts组件是Ansible用于采集被控节点机器的设备信息,比如IP地址、操作系统、以太网设备、mac 地址、时间/日期相关数据,硬件信息等 - setup模块 - 用于获取所有facts信息 shell ## 常用参数 filter…...
Springboot JPA ShardingSphere 根据年分表java详细代码Demo
一、项目结构 src/main/java ├── com.example │ ├── config │ │ └── TableInitializer.java # 动态建表配置 │ ├── entity │ │ └── Order.java # JPA实体类 │ ├── repository │ │ └── OrderRepository.j…...
mapbox进阶,使用本地dem数据,加载hillshade山体阴影图层
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️hillshade 山体阴影图层 api1.3.1 ☘️…...
【C++】Stack Queue 仿函数
📝前言: 这篇文章我们来讲讲STL中的stack和queue。因为前面我们已经有了string、vector和list的学习基础,所以这篇文章主要关注一些stack和queue的细节问题,以及了解一下deque(缝合怪)和priority_queue &am…...
代码随想录_单调栈
代码随想录_单调栈 739.每日温度 739. 每日温度 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,…...
C++类与对象进阶知识深度解析
目录 一、再谈构造函数 (一)构造函数体赋值 (二)初始化列表 (三)成员变量初始化顺序 (四)explicit关键字 二、static成员 (一)概念 (二&am…...
BoostSearch搜索引擎项目 —— 测试用例设计 + web自动化测试代码
web自动化代码: https://gitee.com/chicken-c/boost-search/tree/master/AutoTest...
【Ansible自动化运维】一、初步了解,开启自动化运维之旅
在当今数字化时代,随着企业 IT 基础设施规模的不断扩大,传统的手工运维方式逐渐显得力不从心。自动化运维技术应运而生,其中 Ansible 凭借其简洁易用、功能强大的特点,成为众多运维工程师和开发人员的首选工具。本篇文章将从基础概…...
AI日报 - 2025年4月9日
🌟 今日概览(60秒速览) ▎🤖 AGI突破 | DeepSeek AI推出自我原则批判调优(SPCT)新方法 通过GRMs自我创建和批判原则,性能媲美671B参数大模型 ▎💼 商业动向 | NVIDIA发布Llama-Nemotron-Ultra 253B模型 开放权重和训练数据&#x…...
2025年二级建造师考前冲刺题库
二建考前冲刺练习通常会涵盖考试的重点和高频考点,考生在做题过程中可以加深对这些知识点的理解和记忆,提高对重点知识的掌握程度。 建设工程法规及相关知识 1、单选题:关于建设工程中代理的说法,正确的是( …...
蓝桥·20264-祝福语--找连续字串的长度
#include <iostream> using namespace std; int main() {// 请在此输入您的代码//最小字典序,一定是全a,找s的最长字串a,结果就是该字串长度加1(t不能是s的子串)//所以这道题就变成了,找s中字串a出现的长度strin…...
条件概率、概率乘法公式、全概率公式和贝叶斯 (Bayes) 公式
定义 设 P ( A ) > 0 P(A) > 0 P(A)>0,若在随机事件 A A A发生的条件下随机事件 B B B发生的概率记作 P ( B ∣ A ) P(B|A) P(B∣A),定义 P ( B ∣ A ) P ( A B ) P ( A ) P(B|A) \frac{P(AB)}{P(A)} P(B∣A)P(A)P(AB) 则称 P ( B ∣ A ) …...
pdf转latex
Doc2X(https://doc2x.noedgeai.com/) Doc2X 是一个由 NoEdgeAI 提供的在线工具,主要用于将 PDF 文件(尤其是学术论文、报告等文档)转换为 LaTeX 格式。LaTeX 是一种高质量排版系统,广泛应用于学术界和出版…...
【Unity】Unity Transform缩放控制教程:实现3D模型缩放交互,支持按钮/鼠标/手势操作
【Unity 】Transform缩放控制教程:实现3D模型缩放交互,支持按钮/鼠标/手势操作 在Unity开发中,Transform组件承担着场景中物体的空间信息控制,包括位置、旋转和缩放。而缩放(Scale)操作,作为三…...
