Airi 项目实战:深入 VueUse useCloned,实现响应式深拷贝与草稿状态隔离
Airi 项目实战深入 VueUse useCloned实现响应式深拷贝与草稿状态隔离【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airiuseCloned是 VueUse 提供的一个响应式克隆工具它接收一个ref源返回一份独立的克隆副本并让克隆与源保持可配置的同步关系。本指南以 VueUse 官方参考文档为主体结合 Airi 仓库中 provider 配置编辑页的真实源码provider 编辑页讲解useCloned的默认克隆机制、flush、manual、clone三类核心选项以及如何在表单草稿编辑、撤销比较、脏检查等场景中落地使用。读完本文你将能独立判断何时该用useCloned、如何配置同步时机以及如何把它嵌入一个需要编辑不污染源数据的 Vue 应用中。一、useCloned 是什么useCloned是一个ref 的响应式克隆它读取一个源 ref或 getter生成一个独立的clonedref。默认情况下克隆使用JSON.parse(JSON.stringify())完成也就是标准的深拷贝路径——源对象的所有嵌套层级都会被复制到新对象中二者不再共享内存引用。在 .agents/skills/vueuse-functions/SKILL.md 的函数分类表中useCloned被归入Utilities类别调用规则为AUTO即满足需求时可直接自动使用。VueUse 官方参考文档位于 .agents/skills/vueuse-functions/references/useCloned.md其中明确其定位是 Reactive clone of a ref。它的典型价值在于当你需要就地编辑一份数据、但又不希望这些修改立即污染原始状态例如表单草稿、撤销栈快照、配置编辑时useCloned帮你把拷贝 同步这套样板逻辑封装成一个组合式函数而不是手写深拷贝和watch。二、基础用法默认的异步克隆useCloned的最基本用法如下import { useCloned } from vueuse/core const original ref({ key: value }) const { cloned } useCloned(original) original.value.key some new value console.log(cloned.value.key) // value这里有一个容易忽略的关键行为对源的修改不会立即反映到cloned上。原因是useCloned内部通过watch监听源的变化并触发重新克隆而 Vue 的watch回调默认在组件渲染的下一轮flush: pre执行。因此上面代码中修改original.value.key之后立刻读取cloned.value.key拿到的仍是旧值value。对于大多数 UI 场景这种下一帧再同步的行为完全够用而且它天然地把克隆开销挡在渲染关键路径之外。如果你确实需要在修改后同一次同步代码块内读到最新克隆值就需要显式指定同步时机。三、flush: sync立即同步克隆useCloned的选项继承自 Vue 的WatchOptions所以可以直接传入flush来控制内部 watcher 的触发时机。设置为sync后源数据一旦变化克隆会立刻更新const { cloned } useCloned(original, { flush: sync }) original.value.key some new value console.log(cloned.value.key) // some new value适用场景判断flush 取值同步时机适用场景pre默认组件渲染前批量执行常规表单绑定、展示型克隆性能更好sync源修改的同一时刻立即执行需要在同一执行栈内连续读写的逻辑如中转数据、命令式调用post组件渲染后执行需要等待 DOM 更新后再同步克隆的场景注意sync意味着每次源变化都同步触发一次深拷贝。如果源对象很大且变化频繁应优先评估默认pre模式或下面的手动同步方案。四、manual: true 与 sync()把同步权交给自己默认的自动同步在编辑草稿场景里会带来一个问题克隆值被用户改了一半源一旦变化就自动覆盖草稿就丢了。此时应开启手动模式由代码决定何时把源同步进克隆import { useCloned } from vueuse/core const original ref({ key: value }) const { cloned, sync } useCloned(original, { manual: true }) original.value.key manual console.log(cloned.value.key) // value源已变但克隆未变 sync() console.log(cloned.value.key) // manual手动同步后克隆拿到最新源manual: true时useCloned会关闭内部的自动 watch仅保留sync()作为唯一入口。返回值中同时提供isModifiedref用于指示克隆数据相对源是否被修改过这正是表单脏检查dirty check的基础。五、自定义克隆函数 clone默认的JSON.parse(JSON.stringify())有明确的边界它无法保留undefined、function、Date、Map、Set、RegExp等类型且会丢失对象原型。当数据含有这些类型、或对克隆性能有更高要求时可以通过clone选项替换克隆实现。VueUse 官方文档以klona为例import { useCloned } from vueuse/core import { klona } from klona const original ref({ key: value }) const { cloned, isModified, sync } useCloned(original, { clone: klona })klona是一个轻量且性能优异的深拷贝库能正确处理Date、Map、Set、RegExp、TypedArray等类型。使用时需要自行将其加入项目依赖在 Airi 中可通过仓库的 pnpm workspace 统一管理依赖版本参见 pnpm-workspace.yaml。同样地任何满足(source: T) T签名的纯函数都可以作为clone传入——例如针对某个特殊结构手写的浅拷贝函数或带循环引用处理的自定义实现。六、类型声明与完整 APIuseCloned的完整类型定义来自 useCloned.md如下export interface UseClonedOptionsT any extends WatchOptions { /** * Custom clone function. * * By default, it use JSON.parse(JSON.stringify(value)) to clone. */ clone?: (source: T) T /** * Manually sync the ref * * default false */ manual?: boolean } export interface UseClonedReturnT { /** * Cloned ref */ cloned: RefT /** * Ref indicates whether the cloned data is modified */ isModified: Refboolean /** * Sync cloned data with source manually */ sync: () void } export type CloneFnF, T F (x: F) T export declare function cloneFnJSONT(source: T): T export declare function useClonedT( source: MaybeRefOrGetterT, options?: UseClonedOptions, ): UseClonedReturnT要点拆解source: MaybeRefOrGetterT既可以是ref也可以是普通值或 getter 函数与其他 VueUse 组合式函数的入参约定一致。UseClonedOptions继承WatchOptions因此除了clone、manual还能直接使用 Vuewatch的全部选项如deep、immediate、flush。cloneFnJSONT(source: T): T独立的导出函数即默认克隆实现JSON.parse(JSON.stringify())的封装可单独复用。返回值三元组cloned克隆 ref、isModified是否被修改、sync手动同步函数。七、Airi 中的真实应用provider 配置编辑草稿Airi 仓库本身就是一个useCloned的规模化使用案例仓库内所有 Vue 应用stage-web、stage-pocket、stage-tamagotchi、component-calling 等及大量 UI 包stage-ui、stage-pages 等都声明依赖vueuse/coreworkspace 中统一为^14.4.0。其中最典型的使用位于provider 配置编辑页packages/stage-pages/src/pages/v2/settings/providers/edit/[providerId]/index.vue。7.1 草稿克隆编辑不污染 store编辑页从全局 store 读取 provider 配置然后用useCloned克隆出一份草稿用户在表单上的所有操作都只作用于这份草稿// NOTICE: useCloned handles deep cloning and state isolation for the draft. // It provides a cloned ref that we use for editing without affecting the original store state. const { cloned: providerConfigEdit, sync: syncProviderConfigEdit } useCloned(providerConfig, { manual: true })见 index.vue这里选择了{ manual: true }编辑过程中的每一次击键都直接修改providerConfigEdit.value.config但绝不会反向影响 store 中的原始配置。表单字段的写入统一走setFieldValue同样只更新草稿function setFieldValue(key: string, value: unknown) { if (!providerConfigEdit.value) return // NOTICE: Update local draft only. useCloned makes it safe to mutate cloned.value. providerConfigEdit.value.config[key] value }见 index.vue7.2 外部数据变化时手动同步关闭了自动同步后何时把 store 的最新数据拉进草稿编辑页通过一个watch监听源配置只有检测到来自外部的真实变更而不是用户编辑引发的变化时才手动调用syncProviderConfigEdit()watch(providerConfig, (newVal, oldVal) { if (newVal Object.keys(newVal).length 0) { // Only sync the draft if the underlying data in the store has actually changed from an external source. if (JSON.stringify(newVal) ! JSON.stringify(oldVal)) { syncProviderConfigEdit() } } }, { immediate: true })见 index.vue这段代码还展示了useCloned与原生watch的典型协作模式useCloned负责拷贝与隔离watch负责决定何时同步二者各司其职。immediate: true保证页面打开时草稿立即以 store 当前值为基准初始化。7.3 基于克隆值的脏检查与提交编辑是否生效、是否可以跳过校验都直接比对草稿与store 原始值const isEdited computed(() { const currentConfig providerConfigEdit.value?.config ?? emptyProviderConfigValues const savedConfig providerConfig.value?.config ?? emptyProviderConfigValues return JSON.stringify(currentConfig) ! JSON.stringify(savedConfig) })见 index.vue用户点击保存时才把草稿的配置一次性提交回 storeupdateProviderConfig会更新本地状态并尝试同步远端见 packages/stage-ui/src/stores/providers/config.tsfunction commitEditedConfig(status: configured | bypassed) { if (!providerConfigEdit.value) return providerStore.updateProviderConfig(providerId.value, { ...providerConfigEdit.value.config }, status) }见 index.vue这个完整链路克隆草稿 → 就地编辑 → 脏检查 → 提交正是useCloned在真实项目中最具代表性的用法也与官方文档中manualsync的定位完全吻合。八、最佳实践与注意事项结合官方文档的类型约束与 Airi 的实战代码总结如下实践要点默认克隆是 JSON 深拷贝JSON.parse(JSON.stringify())无法保留undefined、function、Date、Map、Set、RegExp等类型。数据含这些类型时用clone选项换实现如klona。别在修改源后立即读克隆值默认flush: pre下克隆是异步更新的同一执行栈内需要最新值用{ flush: sync }。草稿编辑务必用manual: true否则源一变用户未完成的编辑会被自动覆盖。手动模式下配合sync()和原生watch带条件判断决定同步时机。利用isModified做脏检查官方返回值中的isModifiedref 可直接驱动保存 / 放弃按钮的禁用状态Airi 中则用JSON.stringify比对实现等价判断。深拷贝有成本源对象越大、变化越频繁克隆开销越高。对巨型对象考虑浅克隆 结构化共享或仅在提交/重置等关键节点sync()。适用范围useCloned面向响应式克隆与状态隔离若需求是追踪历史记录双向同步防抖/节流后的同步可分别参考同目录下的 useRefHistory.md、syncRefs.md、refDebounced.md 等参考文档按需选用。九、总结useCloned以极小的 API 面cloned/isModified/sync三个返回值clone/manual两个核心选项覆盖了响应式深拷贝 可控同步这一常见需求默认模式下它是简洁的深拷贝派生数据源flush: sync下它提供同栈一致性手动模式下它成为表单草稿与状态隔离的基石。Airi 的 provider 配置编辑页展示了它在真实业务中的完整落地形态——克隆隔离、条件同步、脏检查、提交回写这正是把官方文档用法转化为可复用工程模式的最佳参考。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →