ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Vuex 4 插件机制深入指南:订阅 Mutation、快照调试与内置 Logger 实战

Vuex 4 插件机制深入指南:订阅 Mutation、快照调试与内置 Logger 实战 Vuex 4 插件机制深入指南订阅 Mutation、快照调试与内置 Logger 实战【免费下载链接】vuex️ Centralized State Management for Vue.js.项目地址: https://gitcode.com/gh_mirrors/vu/vuexVuex 的插件系统允许在 store 的整个生命周期中挂载钩子在每个 mutation 提交之后获得拦截与响应机会是实现数据源同步、持久化、日志与调试工具的官方扩展点。本文以 Vuex 4本仓库对应的createStore版本的插件机制为主线系统讲解插件定义、在插件内提交 mutation、状态快照采集以及内置createLogger日志插件的全部配置项并结合 src/store.js、src/plugins/logger.js 等源码与 test/unit/store.spec.js 中的测试用例揭示订阅分发与日志输出的底层实现。插件是什么Vuex 的 store 构造函数接受一个plugins选项它是一组针对每一次 mutation暴露钩子的函数数组。一个 Vuex 插件本质上就是一个只接收 store 作为唯一参数的普通函数const myPlugin (store) { // store 初始化完成后会被调用一次 store.subscribe((mutation, state) { // 每一次 mutation 提交后都会调用 // mutation 的格式为 { type, payload } }) }在创建 store 时通过plugins数组注册即可生效const store createStore({ // ... plugins: [myPlugin] })从源码看插件的调用时机非常明确。在 src/store.js 中Store构造函数在完成模块安装installModule与状态初始化resetStoreState之后会执行// apply plugins plugins.forEach(plugin plugin(this))也就是说插件在 store 的 state、getters、mutations、actions 全部就绪之后被同步调用插件函数内部拿到的是一个完全可用的 store 实例也正因为如此插件可以在自己的作用域里立即访问store.state并注册store.subscribe回调。在插件内提交 Mutation与组件一样插件不允许直接修改 state。插件要改变状态只能通过提交 mutation 来触发——这是 Vuex 单向数据流纪律的延伸任何对状态的写入都必须经过 mutation 处理函数以便状态变更可追踪、可回放、可被严格模式与 DevTools 校验。借助提交 mutation这一能力插件最典型的应用场景是把外部数据源与 store 同步。下面是一个把 WebSocket 数据源同步进 store 的插件工厂函数示例为教学用途真实场景中createWebSocketPlugin通常会再接收若干额外选项以处理更复杂的需求export default function createWebSocketPlugin (socket) { return (store) { socket.on(data, data { store.commit(receiveData, data) }) store.subscribe(mutation { if (mutation.type UPDATE_DATA) { socket.emit(update, mutation.payload) } }) } }使用方式const plugin createWebSocketPlugin(socket) const store createStore({ state, mutations, plugins: [plugin] })这个例子同时展示了插件的两个方向外部 → storesocket 收到data事件时通过store.commit(receiveData, data)把数据写入状态store → 外部通过store.subscribe监听UPDATE_DATAmutation把本地变更推送到远端。底层机制上store.commit在 src/store.js 中完成三件事将参数统一为{ type, payload }形式unifyObjectStyle、在_withCommit保护下依次执行该 type 注册的所有 mutation 处理函数、最后通知订阅者this._subscribers .slice() // 浅拷贝避免订阅者在回调中同步退订导致迭代器失效 .forEach(sub sub(mutation, this.state))注意这里的两个细节订阅者拿到的是{ type, payload }与最新的this.stateslice()浅拷贝保证了订阅者回调内同步调用退订函数也不会破坏遍历该行为由 test/unit/store.spec.js 的测试用例覆盖。状态快照前后状态对比有时插件希望拿到状态的快照并比较 mutation 前后的状态差异。由于 Vuex 的 state 是响应式对象且引用同一实例要实现前后对比必须对 state 对象做深拷贝const myPluginWithSnapshot (store) { let prevState _.cloneDeep(store.state) store.subscribe((mutation, state) { let nextState _.cloneDeep(state) // 比较 prevState 与 nextState ... // 为下一次 mutation 保存状态 prevState nextState }) }一个容易踩的坑是深拷贝必须在store.state上执行。因为 Vuex 的 state 本身经过 Vue 的reactive包装见 src/store-util.js 的store._state reactive({ data: state })直接保存引用只能得到同一个会持续变化的对象无法形成快照。仓库自带的 src/util.js 中实现了一个deepCopy它通过缓存{ original, copy }键值对来处理循环引用对象避免深拷贝递归时死循环——内置 logger 插件正是复用了这个实现。重要警告做状态快照的插件只应在开发阶段使用。因为深拷贝是昂贵的操作会拖慢生产环境性能。如果使用 webpack 或 Browserify 构建可以让构建工具替我们处理const store createStore({ // ... plugins: process.env.NODE_ENV ! production ? [myPluginWithSnapshot] : [] })这样插件默认会被启用进入生产构建时需要把process.env.NODE_ENV ! production替换为false——webpack 下通常使用 DefinePluginBrowserify 下使用 envify 完成这一常量替换从而让打包产物自动移除快照插件。内置 Logger 插件Vuex 内置了一个用于日常调试的日志插件createLoggerimport { createLogger } from vuex const store createStore({ plugins: [createLogger()] })createLogger在 src/index.js 与 src/index.js 中作为具名导出暴露同时通过默认导出挂载在Vuex.createLogger上。它的完整实现位于 src/plugins/logger.js代码注释标明该实现借鉴自fcomb/redux-logger。可配置选项createLogger接受一个选项对象全部字段及默认行为如下以下示例即官方文档给出的完整配置形态const logger createLogger({ collapsed: false, // 自动展开被记录的 mutation 日志默认 true折叠 filter (mutation, stateBefore, stateAfter) { // 返回 true 则记录该 mutation // mutation 的格式为 { type, payload } return mutation.type ! aBlocklistedMutation }, actionFilter (action, state) { // 与 filter 相同但针对 action // action 的格式为 { type, payload } return action.type ! aBlocklistedAction }, transformer (state) { // 在记录日志之前转换 state // 例如只返回某个子树 return state.subTree }, mutationTransformer (mutation) { // mutation 以 { type, payload } 形式被记录 // 可以按任意方式格式化 return mutation.type }, actionTransformer (action) { // 与 mutationTransformer 相同但针对 action return action.type }, logActions: true, // 记录 action 日志 logMutations: true, // 记录 mutation 日志 logger: console, // console API 的实现默认是 console })对照 src/plugins/logger.js 的默认值可以看到选项默认值作用collapsedtrue是否折叠日志分组false时自动展开filter() true决定某个 mutation 是否写入日志transformerstate state记录前对 state 做转换可只输出指定子树mutationTransformermut mut格式化 mutation 的展示形式actionFilter() true决定某个 action 是否写入日志actionTransformeract act格式化 action 的展示形式logMutationstrue是否记录 mutation 日志logActionstrue是否记录 action 日志loggerconsole自定义日志输出对象需实现consoleAPI对应的 TypeScript 类型声明可以在 types/logger.d.ts 中查看LoggerOptionS的泛型S表示 state 类型filter/actionFilter的泛型参数P extends Payload约束为{ type: string; payload?: any }。Logger 的底层实现从 src/plugins/logger.js 的源码可以看清日志插件的完整工作流初始化时prevState deepCopy(store.state)保存初始状态快照记录 mutation通过store.subscribe((mutation, state) ...)订阅在每次 mutation 后deepCopy(state)得到nextState先用filter(mutation, prevState, nextState)判断是否记录再生成带毫秒级时间戳的标题getFormattedTime输出 HH:MM:SS.mmm格式见 src/plugins/logger.js最后依次输出三行带颜色的日志prev state灰色transformer(prevState)mutation蓝色mutationTransformer(mutation)next state绿色transformer(nextState)随后更新prevState nextState为下一次对比做准备记录 action通过store.subscribeAction((action, state) ...)订阅用actionFilter判断、actionTransformer格式化后输出一行action日志蓝色。collapsed选项控制分组方式true时使用logger.groupCollapsed折叠分组false时使用logger.group展开分组startMessage/endMessage都包裹了 try-catch在不支持 group API 的环境中退化为普通logger.log输出见 src/plugins/logger.js。另外logger 文件也可以通过script标签直接以独立文件引入此时会全局暴露createVuexLogger函数对应源码中的createLogger导出。注意Logger 同样会做快照和状态快照小节中的警告一致——logger 插件内部会对 state 做深拷贝每记录一次 mutation 就deepCopy一次这是有成本的因此官方文档明确建议只在开发环境启用 logger。订阅机制的源码与测试验证插件能力的根基是 store 的订阅 API。除了文档中使用的subscribeVuex 4 还提供了subscribeAction二者实现在 src/store.jssubscribe (fn, options) { return genericSubscribe(fn, this._subscribers, options) } subscribeAction (fn, options) { const subs typeof fn function ? { before: fn } : fn return genericSubscribe(subs, this._actionSubscribers, options) }两点值得注意subscribe只监听 mutation在commit完成后触发subscribeAction监听 action并且支持两种调用形式直接传函数等价于只订阅before或传{ before, after, error }对象来分别钩住 action 的启动、成功与失败阶段——after在 action 返回的 Promise resolve 后触发error在 reject 时触发时序细节可见 test/unit/modules.spec.js 与 action 错误订阅测试test/unit/modules.spec.js。两个 API 都返回退订函数。genericSubscribe在 src/store-util.js 中实现订阅时会去重同一函数只注册一次支持options.prepend把回调插入队列头部返回的闭包用于从订阅者数组中移除自身。mutation 订阅回调的调用点则如前文所述位于commit末尾的this._subscribers.slice().forEach(...)。仓库测试对插件的整体行为也有直接覆盖test/unit/modules.spec.js 的plugins用例验证了插件在 store 初始化后立即被调用initState store.state、store.subscribe能收到{ type, payload }与最新 state、store.subscribeAction能在 dispatch 后收到{ type, payload }回调。顺带一提Vuex 的 DevTools 集成同样构建在这套订阅机制之上src/plugins/devtool.js 中addDevtools通过store.subscribe上报 mutation 时间线事件、通过store.subscribeAction({ before, after })记录 action 的 start/end 及耗时。这说明理解subscribe/subscribeAction是理解整个 Vuex 可观测生态日志、DevTools、调试工具的前提。插件开发注意事项综合文档与源码编写 Vuex 插件时有几点实践建议插件是纯函数式的扩展点它只接收 store在 store 初始化完成后同步执行不要在插件里直接改写store.state必须走commit否则在严格模式下会触发do not mutate vuex store state outside mutation handlers断言见 src/store-util.js。深拷贝只在需要快照对比时使用快照与 logger 插件成本高务必配合process.env.NODE_ENV ! production与构建工具webpack DefinePlugin / Browserify envify做到生产环境剔除。及时退订subscribe与subscribeAction返回退订函数用于在插件生命周期结束时清理回调避免内存泄漏。过滤器是性能与隐私的开关filter/actionFilter既可以屏蔽敏感或高频的 mutation/action也可以显著减少日志输出量。进一步阅读插件的完整概念讲解可参考中文版 docs/zh/guide/plugins.md 与英文版 docs/guide/plugins.mdcreateLogger的源码位于 src/plugins/logger.js类型声明见 types/logger.d.ts订阅机制的实现与测试分别在 src/store.js 与 test/unit/store.spec.js。【免费下载链接】vuex️ Centralized State Management for Vue.js.项目地址: https://gitcode.com/gh_mirrors/vu/vuex创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表