[React] 如何用 Zustand 构建一个响应式 Enum Store?附 RTKQ 实战与 TS 架构落地
[React] 如何用 Zustand 构建一个响应式 Enum Store?附 RTKQ 实战与 TS 架构落地
本文所有案例与数据为作者自行构建,所有内容均为技术抽象示例,不涉及任何实际商业项目
自从之前尝试了一下 zustand 之后,就发现 zustand 是一个轻量但功能强大的状态管理工具。在我们目前的企业级项目中,它不能完全取代 Redux Toolkit,但却可以很好地作为 enhancer,尤其适用于像 enum 管理这种状态简单但变化频繁的场景
背景介绍
简单的描述一下我们现在有的功能,以及我想要提升的部分
我们的项目是一个大型的 B2B 的项目,因此后端部分的检查配置特别多,这也就导致我们有很多的 enum——真的很多,一千多行,上百个 enums,而这只是在我内部使用的 data definition 文件中的。前端的业务相对而言会更加的复杂,尤其是 UI 需要基于不同的业务场景,将本来的 enum 进行切片,只渲染与当前业务场景相关的 enum
对于前端来说,这就意味着我们需要对目前的 enum 进行更细致的切割,也就导致了代码量一涨又涨,管理起来也非常的困难
举例说明就是,一个学校会有很多的课:语数外理化生、体育、美术等,但是不同专业的学生要选的课肯定是不一样的,比如说理科专业的,那专业课里就不会涉及到写生、艺术赏析这种课程,反之亦然
我们现在的业务场景和这个情况就有点相似,前端方面,就像选专业课的时候,列举的一定是本专业相关的课程;后端方面,它只需要验证提交的课是合法的课程,并不需要判断学生的专业身份
除了静态的 enum 需求,UI 部分还会有另外的挑战,也就是必须要将动态的数据保存下来,同样作为 dropdown 去进行渲染。有些数据对于动态数据的精确度要求比较高,需要做外链检查,因此保存的是 id——大多数情况下做的是 referential integrity 检查;另外的情况下,这些动态的数据会作为对象的属性值保存,因此保存的不是 id,而是值——这种情况下作为 record 保存,不需要在意数据是否过期/被删除
我们目前对此的处理方法,是在 redux state 中保存两份 copy,一份是以值为主的数组,另一份则是以 键值对 的格式进行保存——这么做的主要原因也是为了集中业务处理的逻辑,避免到处使用 useMemo
造成的逻辑散乱
之前一直想着要优化一下 DX 方面,不过最终也没什么更好的处理方法,一直到最近,了解了一下 zustand,发现这个搭配 RTK/RTKQ 能够很好的解决我们现在有的一些问题——根据 How to access Zustand store outside a functional component? 这个 post,我们可以在 react 之外比较轻松的 set/get zustand 数据,这个比起到处乱传 redux state 要简单不少
设计结构
这里我们需要分成两个部分进行思考设计:
-
静态数据
静态数据是固定的 enum,这些和 DD 文件中设定好的 enum 一致,我们只是想要一个更好的方法去分类对应的 enums
-
动态数据
动态数据来自于后端,我们需要一个弱定义,只需要确定保存的格式为 键值对 即可,也就是用
Record<string, string>
去进行一个宽泛的限制
这里依旧以课程作为一个宽泛的案例进行设定,其中:
-
静态数据
这里包含了课程、专业、教授
这里数据的关联为:
不同专业需要包含不同的课程;教授可以教不同的课
-
动态数据
这里我没有太好的案例,就通过异步获取课程的等级和授课的时间分区
主要是这地方的数据和静态数据不需要产生太多的关联,因此也没有什么特别好的案例,就先这么设定吧
静态数据
基础的 enum 设定
这里就是比较宽泛的包含所有的 enum,并且用 object 的方式进行存储,用 as const
的方式可以更好的获取定义——这里的数据也的确不用修改
具体的实现如下:
export const enums = {courses: {ml: "Machine Learning",db: "Database Systems",ds: "Data Structures",stats: "Statistics",},majors: {ai: "Artificial Intelligence",se: "Software Engineering",da: "Data Analytics",},professors: {p1: "Dr. Ada Lovelace",p2: "Dr. Alan Turing",p3: "Dr. Andrew Ng",},
} as const;export type EnumMap = Record<string, string>;
export type EnumKey = keyof typeof enums;
重申一下,EnumMap
设置成宽泛的 Record<string, string>
是为了之后动态数据做准备。静态数据不会被修改,动态数据难以被规范,因此这里只需要一个比较宽泛的 键值对 规范即可
enum slice 的设定
enum slice 的设定与 enum 相似,它包含的是数据的切片,当然,这里的 slice 看起来更像是两个属性之间的 mapping:
import { EnumSliceDefinition } from "./type";export const enumSlices = {majorCourses: {ai: ["ml", "db", "stats"],se: ["db", "ds"],da: ["db", "stats"],},professorCourses: {p1: ["db", "stats"],p2: ["ds", "ml"],p3: ["ml"],},
} satisfies {majorCourses: EnumSliceDefinition<"courses">;professorCourses: EnumSliceDefinition<"courses">;
};export type EnumSlices = typeof enumSlices;
export type EnumSliceKey = keyof EnumSlices;
export type EnumSliceDefinition<K extends EnumKey> = Record<string,(keyof (typeof enums)[K])[]
>;
export type SliceId<K extends EnumSliceKey> = keyof EnumSlices[K];
export type SliceValue<K extends EnumSliceKey> =EnumSlices[K][SliceId<K>] extends readonly (infer V)[] ? V : never;
satisfies
这么写的优点在于更好的 TS 提示——之前提到了,enum 的数据量非常大,要有上千行/上千条的数据,手动写的话,一旦出现 typo,就会导致数据出现异常
这样实现的效果如下:
这个情况下,对于比较简单,并且只有静态的数据格式的情况下,其实已经可以直接使用了:
至此,静态数据的封装已经完成的差不多,接下来要完成的是动态数据的封装
Zustand 设计
zustand 的设计相对而言比较简单,因为我们的需求和功能都是已经定义好的:
-
静态数据和动态数据存储方式都是键值对
为了方便类型断言,动态数据、静态数据可以分开存储
获取数据时,可以获取 键值对 的格式,也可以获取 values 的格式
-
可以获取静态数据
-
可以获取和保存动态数据数据
-
需要可以根据情况获取 sliced enum
具体的实现如下:
export const useEnumStore = create<EnumState>((set, get) => ({enums,slices: enumSlices,refEnums: {},getEnumOptions: (key) => {const entries = Object.entries(get().enums[key]);return entries.map(([id, label]) => ({ id, label }));},getEnumValues: (key) => {return Object.keys(enums[key]);},getRefEnumOptions: (key) => {const refEnum = get().refEnums[key] ?? {};return Object.entries(refEnum).map(([id, label]) => ({ id, label }));},getRefEnumValues: (key) => {const refEnum = get().refEnums[key] ?? {};return Object.values(refEnum);},getSliceValues: (key, id) => {return get().slices[key][id] as SliceValue<typeof key>[];},getSliceOptions: (sliceKey, id) => {const values = get().slices[sliceKey][id] as SliceValue<typeof sliceKey>[];const enumKeyMap: Partial<Record<EnumSliceKey, EnumKey>> = {majorCourses: "courses",professorCourses: "courses",};const enumKey = enumKeyMap[sliceKey];const labelMap = enumKey ? get().enums[enumKey] : {};return values.map((val) => ({id: val,label: labelMap?.[val as keyof typeof labelMap] ?? val,}));},getSliceKeysByValue: (key, value) => {const slice = get().slices[key];return Object.entries(slice).filter(([, list]) => list.includes(value)).map(([id]) => id as SliceId<typeof key>);},setRefEnum: (key, data) => {set((state) => ({refEnums: {...state.refEnums,[key]: data,},}));},
}));
数据的类型格式如下:
export interface EnumState {enums: typeof enums;slices: typeof enumSlices;refEnums: Partial<Record<RefEnumKey, EnumMap>>;getEnumOptions: <K extends EnumKey>(key: K) => { id: string; label: string }[];getEnumValues: <K extends EnumKey>(key: K) => string[];getRefEnumOptions: (key: RefEnumKey) => { id: string; label: string }[];getRefEnumValues: (key: RefEnumKey) => string[];getSliceValues: <K extends EnumSliceKey>(key: K,id: SliceId<K>) => SliceValue<K>[];getSliceOptions: <K extends EnumSliceKey>(sliceKey: K,id: SliceId<K>) => { id: SliceValue<K>; label: string }[];getSliceKeysByValue: <K extends EnumSliceKey>(key: K,value: SliceValue<K>) => SliceId<K>[];setRefEnum: (key: RefEnumKey, data: EnumMap) => void;
}
这里实现的功能有:
-
getEnumOptions
这是一个将当前静态 enum 返回成
{id: key, label: value}
格式的方法,需要的参数是对应的 enum 的 key目前来说这个方法其实没办法很好的用在 hooks 里,我大概试过两三种写法,最大的问题/冲突在于,options 会获取原本的
{key: value}
值转化为{id: key, label: value}
这个格式——我用的是 Array.map 做的。Array.map 的特性就在于,会返回一个新的数组,所以会导致组件无限重复渲染如果使用
state.getState().getEnumOptions(...)
的写法倒是可以避免这个问题,但是这种写法没办法自动追踪/订阅 zustand 的最新数据变化,导致数据更新后,UI 方没办法正确更新 -
getEnumValues
同
getEnumOptions
-
setRefEnum
这个是必须的,毕竟 refEnum 是需要异步获取的,所以我们必须要有合适的方法去添加对应的数据
-
getRefEnumOptions
同上
-
getRefEnumValues
同上 -
getSliceValues
同上 -
getSliceOptions
同上 -
getSliceKeysByValue
这其实是一个 util 的方法,可以通过提供的 value 去获取哪些 slice 包含当前的值,
👀:这里的 Object.entries
其实有点重复功能,比较好的处理方法还是和 Object.keys
& Object.values
一起抽出来做成一个 util 方法,现在就偷了个懒,直接在 hooks 里和 zustand 里写死了值
zustand & redux 之间的比较
只是针对当前这个无限循环问题来说,zustand 和 redux 一样,都会导致无限循环的问题
本质上来说,双方的追踪机制让一旦 getter/reducer 里面出现的值的引用发生了变化,那么重新渲染就会发生 -> 这时候也会触发 React 的 re-render
回顾一下这个方法的实现:
getEnumOptions: (key) => {const entries = Object.entries(get().enums[key]);return entries.map(([id, label]) => ({ id, label }));},
本质上来说,这就是应该在 immutable 的 reducer 这里,无限期的创立了新的 reference,这种情况下,不管是 zustand 还是 redux 都没有办法很好的解决无限渲染的问题
如果是在 setter/action 的话就是另一个比较了,目前 zustand 是没有内置支持 immutable 相关的支持,所以如果要实现 immutable change,要么手动操作,要么添加 immer 作为 middleware
相比较而言 redux 内置支持了 immer,这方面是不用考虑的
⚠️:我这里虽然用 getter/reducer,但是二者本质上不是一个概念。reducer 是 redux 核心的一个概念,是更新状态的最后一步,将 action 更新过的状态保存到最终的 store 里,这是一个纯函数,不能有任何的副作用;zustand 的 getter,如其名,只是从 zustand 的状态中获取数据的 util function,并不涉及到 zustand 的数据更新
顺便补充说明一下,为什么现在的实现用不上这些 getter,我还是写了,这是打算之后做 event bus 的时候,可以提供给 yup/zod 去进行一个 subscribe 的实现,当然,目前只是一个 placeholder,是为了 future proof 的实现
我之前也写过一个比较简单的 event bus 的实现:[React 进阶系列] useSyncExternalStore hook,它具体的实现逻辑为:
-
react 侧通过
useSyncExternalStore
监听事件变化 -
redux 中通过
EventEmitter
触发 schema 变化逻辑 -
schema 组件手动订阅并更新
现在虽然能够解决业务难点,代码也不算特别复杂,的确可用,但这个架构的问题在于:
-
事件分发逻辑散落在多个模块中
-
redux 负责数据源,又要额外承担事件调度,职责边界变得模糊 -> 违反了 SPR
-
schema 更新逻辑难以追踪与维护
而 zustand 本身具备非常轻量的 pub/sub 机制 —— 它的 store 就是事件源。如果我们能在 redux 完成异步请求之后,仅更新 zustand 中的 refEnum 数据,由 zustand 本身提供的 publisher 去进行事件的 broadcasting,那么:
-
redux 只需专注于业务数据获取与管理
-
zustand 负责 enum 订阅、通知与响应式 schema 构建
-
组件侧通过自定义 hook 订阅 enum 变化,自动联动 UI -> react 中的组件 component 还是一个 reference,如果 schema 内部可以完成对应的变动,而不是创建一个新的 reference,那么 react 还是可以通过 reference 获取最新的 schema 和 structure
⚠️:这个地方还是需要思考一下怎么完成具体的实现,或许本身还是需要通过
useMemo
或者useCallback
去进行一个 guard,毕竟 react-table 需要 memoize 数据和结构
这种方式不仅将状态与事件解耦,也让 enum 的更新成为一种 被动响应式能力,而不是 主动命令式调用
动态数据
动态数据的获取比较简单,我们需要整合的数据
我们目前的 migration 是往 RTKQ 走——RTKQ 提供了对于 api 的 cache,这部分的功能是 zustand 无法代替,手动实现也非常麻烦。所以从异步调用 API 的这个使用案例来说,zustand 别说代替了,就是碰都没办法碰瓷 RTKQ
本地的 dummy 实现如下:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { useEnumStore } from "../../enum-store/useEnumStore";export const refEnumApi = createApi({reducerPath: "refEnumApi",baseQuery: fetchBaseQuery({ baseUrl: "/" }),endpoints: (builder) => ({getCourseLevels: builder.query<Record<string, string>, void>({queryFn: async () => {await new Promise((r) => setTimeout(r, 300));return {data: {beginner: "Beginner",intermediate: "Intermediate",advanced: "Advanced",},};},async onQueryStarted(_, { queryFulfilled }) {try {const { data } = await queryFulfilled;useEnumStore.getState().setRefEnum("courseLevels", data);} catch (e) {console.error("Failed to set courseLevels enum:", e);}},}),getTimeSlots: builder.query<Record<string, string>, void>({queryFn: async () => {await new Promise((r) => setTimeout(r, 300));return {data: {morning: "Morning",afternoon: "Afternoon",evening: "Evening",},};},async onQueryStarted(_, { queryFulfilled }) {try {const { data } = await queryFulfilled;useEnumStore.getState().setRefEnum("timeSlots", data);} catch (e) {console.error("Failed to set timeSlots enum:", e);}},}),}),
});export const { useGetCourseLevelsQuery, useGetTimeSlotsQuery } = refEnumApi;
这里也没什么特别复杂的逻辑,RTKQ 这部分的支持真的没话说,而且 zustand 方面配置的也很好,intellisense 的支持非常完美,可以最大条件的避免 typo:
hooks
这里实现了几个 hooks 是能够更简单的让 React 能够获取想要的数据,相对而言的实现比较简单,这里就列举获取 slice 和直接获取静态/动态数据的 hooks
获取动态/静态 options
这个也是我尝试了好几个不同的版本,最终敲下来的结果:
import { useMemo } from "react";
import { refEnumKeys } from "../enum-store/enum-config";
import { EnumKey, RefEnumKey } from "../enum-store/type";
import { useEnumStore } from "../enum-store/useEnumStore";export function useEnumOptions(key: EnumKey | RefEnumKey) {const isRef = (refEnumKeys as readonly string[]).includes(key);const enums = useEnumStore((s) =>isRef ? s.refEnums[key as RefEnumKey] : s.enums[key as EnumKey]);return useMemo(() => {return Object.entries(enums ?? {}).map(([id, label]) => ({ id, label }));}, [enums]);
}
像以前提到过的,如果直接使用已经封装好的 getRefEnumOptions
,会造成无限循环 或 无法获取最新数据的情况,这种情况下已经是最好的写法了
主要问题还是这个 Object.entries().map()
会造成的无限渲染,所以这里需要用 useMemo
进行 guard
获取 slice options
import { useMemo } from "react";
import { EnumSliceKey, SliceId, SliceValue } from "../enum-store/type";
import { useEnumStore } from "../enum-store/useEnumStore";export function useEnumSliceOptions<K extends EnumSliceKey>(sliceKey: K,id: SliceId<K>
): { id: SliceValue<K>; label: string }[] {const getSliceOptions = useEnumStore((s) => s.getSliceOptions);return useMemo(() => {return getSliceOptions(sliceKey, id);}, [getSliceOptions, sliceKey, id]);
}
和上面的实现基本一致
调用
最终调用方法如下:
import "./App.css";
import {useGetCourseLevelsQuery,useGetTimeSlotsQuery,
} from "./store/features/refEnumApi";
import { useEnumOptions, useEnumSliceOptions } from "./hooks/enumHooks";function App() {const courseLevels = useEnumOptions("courseLevels");const timeSlots = useEnumOptions("timeSlots");const courses = useEnumOptions("courses");const aiCourseOptions = useEnumSliceOptions("majorCourses", "ai");useGetCourseLevelsQuery();useGetTimeSlotsQuery();return (<div><h3>Course Levels</h3><ul>{courseLevels.map((opt) => (<li key={opt.id}>{opt.id} - {opt.label}</li>))}</ul><h3>Time Slots</h3><ul>{timeSlots.map((opt) => (<li key={opt.id}>{opt.id} - {opt.label}</li>))}</ul><h3>Courses</h3><ul>{courses.map((opt) => (<li key={opt.id}>{opt.id} - {opt.label}</li>))}</ul><h3>AI Courses</h3><ul>{aiCourseOptions.map((opt) => (<li key={opt.id}>{opt.id} - {opt.label}</li>))}</ul></div>);return <></>;
}export default App;
这里的提示依旧很好:
options 和 slice 都能够正确的提示到,最大程度地避免了 typo 的问题——很多时候我们必须要反复的用 Object/keys 去做 typing,就是为了避免这个问题,这里已经最大程度地避免了重复且无意义的 declaration
最终渲染结果如下:
其中短时间内 course level 和 time slot 消失不见的原因是因为页面刷行,RTKQ 重新“拉”数据,导致的短期延时,但是总体来说,渲染的结果是没有问题的,后期想要非常简单的将 options 转成 dropdown 也非常的简单,毕竟已经是固定的 {id, label}
的格式,想要怎么转都很简单
相关文章:
[React] 如何用 Zustand 构建一个响应式 Enum Store?附 RTKQ 实战与 TS 架构落地
[React] 如何用 Zustand 构建一个响应式 Enum Store?附 RTKQ 实战与 TS 架构落地 本文所有案例与数据为作者自行构建,所有内容均为技术抽象示例,不涉及任何实际商业项目 自从之前尝试了一下 zustand 之后,就发现 zustand 是一个轻…...
DeepSeek在职场办公中的高效指令运用与策略优化
摘要 随着人工智能技术的飞速发展,大型语言模型在各个领域的应用日益广泛。DeepSeek作为一款具有影响力的AI产品,为职场办公带来了新的变革与机遇。本文深入剖析DeepSeek在职场办公场景下的提示词指令运用,通过对提示词概念、作用、设计原则的…...
mysql事务脏读 不可重复读 幻读 事务隔离级别关系
看了很多文档,发现针对事务并发执行过程中的数据一致性问题,即脏读、不可重复读、幻读的解释一塌糊涂,这也不能说什么,因为官方SQL标准中的定义也模糊不清。 按照mysql中遵循的事务隔离级别,可以梳理一下其中的关系 隔…...
Fork/Join框架与线程池对比分析
Fork/Join框架与线程池对比分析 1. 概述 线程池(如ThreadPoolExecutor)是Java并发编程中用于管理线程生命周期的通用工具,适用于处理大量独立任务。Fork/Join框架(基于ForkJoinPool)是Java 7引入的专用框架ÿ…...
docker 安装 Gitlab
GitLab 安装 #创建容器数据卷映射目录 mkdir -p /usr/docker/gitlab/config mkdir -p /usr/docker/gitlab/logs mkdir -p /usr/docker/gitlab/data #目录授权 chmod 777 -R /usr/docker/gitlab/*#直接复制可用(记得改下宿主机ipv4,不知道怎么看,输入i…...
【贪心之摆动序列】
题目: 分析: 这里我们使用题目中给的第二个实例来进行分析 题目中要求我们序列当中有多少个摆动序列,摆动序列满足一上一下,一下一上,这样是摆动序列,并且要输出摆动序列的最长长度 通过上面的图我们可以…...
kubectl修改资源时添加注解
kubectl修改资源时添加注解 kubectl修改资源时添加注解老版本的注解(变化注解)删除Annotations查看Annotations信息 查看发布记录回滚 kubectl修改资源时添加注解 参考: 为什么我们要使用kubectl apply 修改资源时,在命令行后添加 --save-configtrue ,就会自动添加此次修改的…...
【C++初学】课后作业汇总复习(四) 复数类与运算符重载
1、复数类输出 如题,要求实现: 1、复数类含两个参数的构造函数,一个为实部,一个为虚部 2、用Show()现实复数的值。 输出 (23i) //如题,要求实现: // //1、复数类含两个参数的构造函数&…...
十四、C++速通秘籍—函数式编程
目录 上一章节: 一、引言 一、函数式编程基础 三、Lambda 表达式 作用: Lambda 表达式捕获值的方式: 注意: 四、函数对象 函数对象与普通函数对比: 五、函数适配器 1、适配普通函数 2、适配 Lambda 表达式 …...
复刻系列-星穹铁道 3.2 版本先行展示页
复刻星穹铁道 3.2 版本先行展示页 0. 视频 手搓~星穹铁道~展示页~~~ 1. 基本信息 作者: 啊是特嗷桃系列: 复刻系列官方的网站: 《崩坏:星穹铁道》3.2版本「走过安眠地的花丛」专题展示页现已上线复刻的网…...
阿里云备案有必要选择备案管家服务吗?自己ICP备案可以吗?
阿里云备案有必要选择备案管家服务吗?新手可以选择备案管家,备案管家不需要自己手动操作,可以高效顺利通过ICP备案。自己ICP备案可以吗?自己备案也可以的,也很简单,适合动手能力强的同学。 阿里云备案管家…...
SQL语言基础(二)--以postersql为例
上次教程我们讲述了数据库中的增,删,改语句,今天我们来学习最后一个–‘改’的语句。 1.select语法 数据库查询只有select一个句子,但select语法相对复杂,其功能丰富,使用方式也很灵活 SELECT [ALL|Dist…...
探索 Rust 语言:高效、安全与并发的完美融合
在当今的编程语言领域,Rust 正以其独特的魅力吸引着越来越多开发者的目光。它诞生于 Mozilla 实验室,旨在解决系统编程中长久以来存在的难题,如今已成为构建可靠、高效软件的有力工具。 1 内存安全 Rust 通过所有权(ownership&a…...
最大公约数和最小倍数 java
在Java中,计算两个数的最大公约数(Greatest Common Divisor, GCD)和最小公倍数(Least Common Multiple, LCM)是常见的编程问题。以下是具体的实现方法和代码示例。 --- ### **1. 最大公约数 (GCD)** 最大公约数是指…...
OpenHarmony Camera开发指导(三):相机设备输入输出(ArkTS)
相机应用可通过创建相机输入流调用并控制相机设备,创建不同类型的输出流,进而实现预览、拍照、录像等基础功能。 开发步骤 在创建相机设备输入之前需要先完成相机设备管理,详细开发步骤可参考上一篇文章。 创建相机输入流 通过cameraMana…...
通过分治策略解决内存限制问题完成大型Hive表数据的去重的PySpark代码实现
在Hive集群中,有一张历史交易记录表,要从这张历史交易记录表中抽取一年的数据按某些字段进行Spark去重,由于这一年的数据超过整个集群的内存容量,需要分解成每个月的数据,分别用Spark去重,并保存为Parquet文…...
融媒体中心智能语音识别系统设计与实现
县级融媒体中心智能语音识别系统设计与实现 序言 随着融媒体时代的快速发展,新闻采编、专题节目制作对语音转写效率的要求日益提高。作为基层融媒体中心的技术工程师,我们在实际工作中常面临以下痛点: 采访录音整理耗时:传统人…...
学习笔记九——Rust所有权机制
🦀 Rust 所有权机制 📚 目录 什么是值类型和引用类型?值语义和引用语义?什么是所有权?为什么 Rust 需要它?所有权的三大原则(修正版)移动语义 vs 复制语义:变量赋值到底…...
计算机视觉算法实现——电梯禁止电瓶车进入检测:原理、实现与行业应用(主页有源码)
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 1. 电梯安全检测领域概述 近年来,随着电动自行车(以下简称"电瓶车"&…...
扩散模型 Diffusion Model 整体流程详解
🧠 Diffusion Model 思路、疑问和代码 文章目录 🧠 Diffusion Model 思路、疑问和代码🔄 一、核心思想:从噪声到图像📦 二、正向过程:加噪🧠 三、反向过程:学习去噪🎯 目…...
[Spark]深入解密Spark SQL源码:Catalyst框架如何优雅地解析你的SQL
本文内容组织形式 总结具体例子执行语句解析层优化层物理计划层执行层 猜你喜欢PS 总结 先写个总结,接下来会分别产出各个部分的源码解析,Spark SQL主要分为以下五个执行部分。 具体例子 接下来举个具体的例子来说明 执行语句 SELECT name, age FR…...
【数据结构_7】栈和队列(上)
一、概念 栈和队列,也是基于顺序表和链表实现的 栈是一种特殊的线性表,其只允许在固定的一段进行插入和删除元素操作。 遵循后进先出的原则 此处所见到的栈,本质上就是一个顺序表/链表,但是,实在顺序表/链表的基础…...
Linux中的cat命令常见用法
在 Linux 中,cat 命令是 concatenate(连接)的缩写,意思是“连接”或“串联”。 基本功能 cat 命令的主要功能是用于查看、合并和创建文件。它会将一个或多个文件的内容输出到标准输出设备(通常是终端屏幕)…...
css - 实现三角形 div 容器,用css画一个三角形(提供示例源码)简单粗暴几行代码搞定!
效果图 如下图所示,让一个 div 变成三角形,并且可随意更改大小, 本文提供了可运行示例源码,直接复制即可。 实现源码 建议创建一个 demo.html 文件,一键复制代码运行。 <style> .div{width: 0px;height: 0px…...
springboot 项目 jmeter简单测试流程
测试内容为 主机地址随机数 package com.hainiu.example;import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotat…...
设计模式实践:模板方法、观察者与策略模式详解
目录 1 模板方法1.1 模板方法基本概念1.2 实验1.2.1 未使用模板方法实现代码1.2.2 使用模板方法的代码 2 观察者模式2.1 观察者模式基本概念2.2 实验 3 策略模式3.1 策略模式基本概念3.2 实验 1 模板方法 1.1 模板方法基本概念 定义:一个操作中的算法的骨架 &…...
Google的AI模型Gemini和Gemini网络协议
粉丝私信问我:gemini如何访问? "Gemini如何访问"需明确区分两种完全不同的技术体系:Google的AI模型Gemini和Gemini网络协议。以下是两者的访问方式详解: 一、访问Google的Gemini AI模型 1. 通过Web应用 地址…...
HTTP实现心跳模块
HTTP实现心跳模块 使用轻量级的cHTTP库cpp-httplib重现实现HTTP心跳模块 头文件HttplibHeartbeat.h #ifndef HTTPLIB_HEARTBEAT_H #define HTTPLIB_HEARTBEAT_H#include <string> #include <thread> #include <atomic> #include <chrono> #include …...
基于web的民宿信息系统(源码+lw+部署文档+讲解),源码可白嫖!
摘要 随着信息时代的来临,民宿过去的民宿信息方式的缺点逐渐暴露,对过去的民宿信息的缺点进行分析,采取计算机方式构建民宿信息系统。本文通过阅读相关文献,研究国内外相关技术,提出了一种民宿信息管理、民宿信息管理…...
使用OpenSceneGraph (osg)实现一个星系漫游
简介 使用OpenSceneGraph (osg)实现了一个太阳系漫游的程序,具有以下特点: 1.通过按键控制飞行器前进后退、空间姿态; 2.星系渲染; 3.背景星空渲染; 效果 提供了一张超大的星空背景图 代码示例 int main(int a…...
笔试专题(九)
文章目录 十字爆破(暴力)题解代码 比那名居的桃子(滑动窗口/前缀和)题解代码 分组(暴力枚举 优化二分)题解代码 十字爆破(暴力) 题目链接 题解 1. 暴力 预处理 2. 如果单纯的暴…...
sklearn决策树 待更新
注意:sklearn中所有的决策树模型包括回归决策树实现的是CART决策树算法,在官方文档中有介绍。sklearn中的决策树模型最终得到的树结构都是二叉树,因为CART算法生成的就是二叉树。 DecisionTreeClassifier类 如果待预测样本有多个类别具有相同…...
eino v0.3.21 重磅发布!节点中断控制+空值映射支持,AI应用开发再添神器!
CloudWeGo/eino v0.3.21 作为最新补丁版本,聚焦流程控制与数据映射两大核心场景,为AI应用与微服务开发者提供更灵活的调试能力与容错设计! 1. 节点中断控制(Feat: Node Interrupt) • 功能亮点:新增 node …...
力扣每日打卡 50. Pow(x, n) (中等)
[TOC](力扣 50. Pow(x, n) 中等) 前言 这是刷算法题的第十一天,用到的语言是JS 题目:力扣 50. Pow(x, n) (中等) 一、题目内容 实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。 示例 1࿱…...
Mac M1管理多个Node.js版本
目录 1. 使用 nvm (Node Version Manager) 1.1.安装 nvm 1.2.安装Node.js版本 1.3.查看已安装的node版本列表 1.4.使用特定版本的Node.js 1.5.查看当前使用的版本 2. 使用 fnm (Fast Node Manager) 2.1.安装 fnm 2.2.安装Node.js版本 2.3.查看已安装的版本 2.4.使用…...
arm_math.h、arm_const_structs.h 和 arm_common_tables.h
在 FOC(Field-Oriented Control,磁场定向控制) 中,arm_math.h、arm_const_structs.h 和 arm_common_tables.h 是 CMSIS-DSP 库的核心组件,用于实现高效的数学运算、预定义结构和查表操作。以下是它们在 FOC 控…...
每天五分钟深度学习:非线性激活函数的导数
本文重点 本文探讨了神经网络中几种常见非线性激活函数(Sigmoid、Tanh、ReLU、Leaky ReLU、ELU、Softmax)的导数特性。通过对各激活函数导数的数学推导与实际应用分析,揭示了不同激活函数在梯度传播、收敛速度及模型表达能力方面的差异。研究发现,ReLU及其变体在计算效率与…...
OpenHarmony5.0.2 USB摄像头适配
开发环境 OpenHarmony5.0.2 RK3568 USB摄像头 遇到问题 编译后插上USB摄像头后打开相机无图像 解决思路 5.0.2版本是支持USB摄像头的,需要修改默认的板载相机配置即可。 修改代码 1、修改配配置 文件地址:vendor/hihope/rk3568/hdf_config/uhdf…...
vue: router基础用法
router基础用法 1.安装router2.配置router3.路由编程1.编程式导航2.声明式导航 1.安装router 在node环境下,直接运行 npm install router42.配置router 创建文件夹并命名为router 在router文件夹中创建index.js index.js示例配置如下: import { creat…...
IDE中使用Spring Data Redis
步骤一:导入Spring Data Redis的maven坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 步骤二:配置Redis数据源 步骤三&…...
【计网】网络交换技术之报文交换(复习自用,了解,重要3)
复习自用的,处理得比较草率,复习的同学或者想看基础的同学可以看看,大佬的话可以不用浪费时间在我的水文上了 另外两种交换技术可以直接点击链接访问相关笔记: 电路交换 分组交换 一、报文交换的定义 报文交换(Me…...
GitLab 17.x 配置 https
文章目录 使用外部 nginx 参考:https://docs.gitlab.com/omnibus/settings/nginx.html 使用内置 nginx 参考:https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-https-manually // 使用自己手工申请证书 $ mkdir /etc/gitlab/ssl $ m…...
中间件--ClickHouse-1--基础介绍(列式存储,MPP架构,分布式计算,SQL支持,向量化执行,亿万级数据秒级查询)
1、概述 ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。它由俄罗斯的互联网巨头Yandex为解决其内部数据分析需求而开发,并于2016年开源。专为大规模数据分析,实时数据分析和复杂查询设计,具有高性能、实时数据和可扩展性等…...
【编写Node接口;接口动态获取VUE文件并异步加载, 并渲染impoort插件使用】
编写Node接口;接口动态获取VUE文件并异步加载, 并渲染impoort插件使用; vue3-sfc-loader主要特征: 编写Node接口:Vue2项目使用:Vue3项目使用:(页面按需加载插件、图片等)主要使用&am…...
vue入门:template 和 JSX
temlplate 和 jsx 最终渲染时都是创建 dom 节点 template 和 JSX 混合使用 <template><div><span>Message: {{ msg }}</span><br/><VNodes :vnodes"getJSXSpan()"/><VNodes :vnodes"getAnchoredHeading(4)"/>…...
[Dify] Dify 本地部署及连接 Ollama 模型全流程指南
在构建私有化智能应用时,Dify 作为一款开源的大模型应用开发平台,具备强大的插件体系和可扩展能力。本文将详细介绍如何在本地环境中部署 Dify,并成功连接本地的 Ollama 模型,解决实际部署过程中常见的问题与错误。 一、本地部署 Dify 步骤详解 1. 安装 Docker 环境(以 W…...
基于PyQt5的Jupyter Notebook转Python工具
一、项目背景与核心价值 在数据科学领域,Jupyter Notebook因其交互特性广受欢迎,但在生产环境中通常需要将其转换为标准Python文件。本文介绍一款基于PyQt5开发的桌面级转换工具,具有以下核心价值: 可视化操作:提供友好的GUI界面,告别命令行操作 批量处理:支持目录递归…...
从自然语言到 JSON 数据交互:探索 MCP 协议的自动化任务实现
好的!以下是基于我们讨论的关于 MCP 协议、JSON 数据交互以及自然语言到 JSON 转换的实现过程的总结,格式化为一篇 CSDN 风格的博客记录。这篇文章将记录你的发现,适合分享给技术社区。 从自然语言到 JSON 数据交互:探索 MCP 协议…...
n8n 本地部署及实践应用,实现零成本自动化运营 Telegram 频道(保证好使)
n8n 本地部署及实践应用,实现零成本自动化运营 Telegram 频道(保证好使) 简介 n8n 介绍 一、高度可定制性 二、丰富的连接器生态 三、自托管部署(本地部署) 四、社区驱动 n8n 的部署 一、前期准备 二、部署步…...
嵌入式学习(37)-STM32串口发送中断的实现
一、概述 项目中需要用到发送中断,所以了解了发送中断的一些知识。 二、应用 状态寄存器USART_SR的复位值为0x00C0H, 也就是第七位TXE和第六位TC复位值为1,而TXE1,表明发送数据寄存器为空, TC1表明发送已完成。 USART_ITConfig(USART1, USA…...