当前位置: 首页 > news >正文

【手写 Vuex 源码】第十二篇 - Vuex 插件机制的实现

一,前言

上一篇,主要介绍了 Vuex 插件的开发,主要涉及以下几个点:

  • Vuex 插件的使用介绍;
  • Vuex 插件开发和使用分析;
  • Vuex 插件机制的分析;

本篇,继续介绍 Vuex 插件机制的实现;


二,Vuex 插件机制分析

通过官方 Vuex 插件所提供的插件机制,创建并实现了一个简易的 Vuex 插件;

Vuex 插件的实现,主要使用 Vuex 提供的以下方法:

  • Vuex 插件的 plugins 数组,提供插件注册功能;
  • store.subscribe:状态变更时的订阅回调功能;
  • store.replaceState:状态替换功能;

本篇继续以 vuex 持久化插件 vuex-persists 为例,逐步实现 Vuex 插件提供的机制核心逻辑;

三,Vuex 插件机制实现

1,Vuex 插件的注册

  • new Vuex.Store 初始化时,处理 options.plugins 数组中注册的插件函数(高阶函数);
  • 收集 Vuex 插件中,通过 store.subscribe 订阅状态变化事件的回调函数 fn 保存到 _subscribes 数组中;
// src/vuex/store.jsexport class Store {constructor(options) {const state = options.state;this._actions = {};this._mutations = {};this._wrappedGetters = {};// 收集通过 store.subcribe 订阅状态变更事件的处理函数 fn// 当 mutation 执行时,触发全部订阅事件执行,返回当前 mutation 和更新后的状态this._subscribes = [];this._modules = new ModuleCollection(options);installModule(this, state, [], this._modules.root);resetStoreVM(this, state);// 依次执行 options 选项中的 plugins 插件,传入当前 store 实例options.plugins.forEach(plugin => plugin(this));}// 提供 store.subscribe 状态变更事件订阅功能// 将回调函数统计收集到 _subscribes 数组中;subscribe(fn) {console.log("订阅 Vuex 状态变化,收集处理函数")this._subscribes.push(fn);console.log("this._subscribes", this._subscribes)}
}

2,subscribe 状态订阅实现

  • 当状态变化时,即 mutation 方法执行时,依次执行 _subscribes 中保存的处理函数 fn,传入当前 mutation 和当前根状态 rootState;
// src/vuex/store.jsconst installModule = (store, rootState, path, module) => {// ...module.forEachMutation((mutation, key) => {store._mutations[namespace + key] = (store._mutations[namespace + key] || []);store._mutations[namespace + key].push((payload) => {mutation.call(store, module.state, payload);// 当 mutation 执行时,依次执行 store.subscribe 状态变更事件订阅的处理函数 fnstore._subscribes.forEach(fn => {console.log("状态更新,依次执行订阅处理")fn(mutation, rootState);})})})// ...module.forEachChild((child, key) => {installModule(store, rootState, path.concat(key), child);})
}export class Store {constructor(options) {const state = options.state;this._actions = {};this._mutations = {};this._wrappedGetters = {};this._subscribes = [];this._modules = new ModuleCollection(options);// 模块安装处理 mutation 时,触发状态变更订阅事件通知installModule(this, state, [], this._modules.root);resetStoreVM(this, state);// 依次执行 options 选项中的 plugins 插件,传入当前 store 实例options.plugins.forEach(plugin => plugin(this));}
}

测试 Vuex 插件的初始化和状态变化时间订阅:

// src/store/index.jsimport Vue from 'vue';
import Vuex from '@/vuex'; // 使用自开发的 Vuex 插件Vue.use(Vuex);function persists() {return function (store) {console.log("----- persists 插件执行 -----")store.subscribe((mutation, state) => {console.log("----- 进入Vuex 插件 store.subscribe 处理函数-----")localStorage.setItem('VUEX:STATE', JSON.stringify(state));})}
}const store = new Vuex.Store({plugins: [persists()],
});export default store;

在 Vuex 插件初始化过程中 plugins 插件被依次执行,并收集插件中通过 store.subscribe 进行状态变更订阅的回调函数 fn;

image.png

点击按钮更新 Vuex 中的状态,在对应 mutation 方法执行时,依次调用订阅函数,通知 Vuex 插件状态发生变化:

image.png

订阅回调中处理:将当前最新的根状态保存至本地存储:

3,replaceState 状态替换实现

  • 当状态更新时,将最新状态保存至本地存储中;
  • 当页面刷新时,读取本地存储并重新设置 Vuex 状态,刷新状态持久化;

插件初始化时,读取本都存储,进行 Vuex 状态同步:

// src/store/index.jsimport Vue from 'vue';
import Vuex from '@/vuex';Vue.use(Vuex);function persists() {return function (store) {console.log("----- persists 插件执行 -----")// 取出本地存储的状态let data = localStorage.getItem('VUEX:STATE');if (data) {console.log("----- 存在本地状态,同步至 Vuex -----")// 如果存在,使用本地状态替换 Vuex 中的状态store.replaceState(JSON.parse(data));}store.subscribe((mutation, state) => {console.log("----- 进入Vuex 插件 store.subscribe 处理函数-----")localStorage.setItem('VUEX:STATE', JSON.stringify(state));})}
}
const store = new Vuex.Store({plugins: [persists()]
});
export default store;

添加 replaceState 实现,并更新插件逻辑,统一使用最新状态进行处理:

// src/vuex/store.js// 通过当前模块路径 path,从最新的根状态上,获取模块的最新状态
function getState(store, path){return path.reduce((newState,current)=>{return newState[current];}, store.state); // replaceState 后的最新状态
}const installModule = (store, rootState, path, module) => {let namespace = store._modules.getNamespaced(path);if (path.length > 0) {let parent = path.slice(0, -1).reduce((memo, current) => {return memo[current]}, rootState)Vue.set(parent, path[path.length - 1], module.state);}module.forEachMutation((mutation, key) => {store._mutations[namespace + key] = (store._mutations[namespace + key] || []);store._mutations[namespace + key].push((payload) => {// 旧:执行 mutation,传入当前模块的 state 状态// mutation.call(store, module.state, payload);// 新:Vuex 持久化需要使用最新的状态,而 module.state是老状态// 需要通过当前路径 path,获取到当前最新的状态mutation.call(store, getState(store,path), payload);store._subscribes.forEach(fn => {// 旧逻辑:使用根状态 rootState// fn(mutation, rootState);// 新逻辑:使用新状态 store.statefn(mutation, store.state);})})})module.forEachAction((action, key) => {store._actions[namespace + key] = (store._actions[namespace + key] || []);store._actions[namespace + key].push((payload) => {action.call(store, store, payload);})})module.forEachGetter((getter, key) => {store._wrappedGetters[namespace + key] = function () {// 旧逻辑:执行对应的 getter 方法,传入当前模块的 state 状态,返回执行结果// return getter(module.state);// 新逻辑:使用最新的状态进行处理return getter(getState(store,path));}})module.forEachChild((child, key) => {installModule(store, rootState, path.concat(key), child);})
}function resetStoreVM(store, state) {const computed = {};store.getters = {};forEachValue(store._wrappedGetters, (fn, key) => {computed[key] = () => {return fn();}Object.defineProperty(store.getters, key, {get: () => store._vm[key]});})store._vm = new Vue({data: {$$state: state},computed});
}export class Store {constructor(options) {const state = options.state;this._actions = {};this._mutations = {};this._wrappedGetters = {};this._subscribes = [];this._modules = new ModuleCollection(options);installModule(this, state, [], this._modules.root);resetStoreVM(this, state);options.plugins.forEach(plugin => plugin(this));}// Vuex 状态替换replaceState(state){this._vm._data.$$state = state}
}

备注:虽然通过 replaceState 实现了 Vuex 插件状态的更新,但此时 Vuex 中的逻辑处理中,依旧使用的是module.staterootState旧状态,需要根据模块路径重新获取对应的新状态进行处理;否则页面不会更新;(涉及 mutation、getter、_subscribes回调传参等相关处理逻辑需更新);

4,插件效果测试

image.png

  • 点击按钮更新状态,视图与本地存储同步变化;
  • 页面刷新后,状态持久化成功;

四,结尾

本篇,主要介绍了 Vuex 插件机制的实现,主要涉及以下几个点:

  • Vuex 插件机制分析;
  • Vuex 插件机制核心逻辑实现:plugins 插件注册、subscribe 订阅收集、replaceState 状态替换;

下一篇,继续介绍 Vuex 辅助函数的实现;

相关文章:

【手写 Vuex 源码】第十二篇 - Vuex 插件机制的实现

一,前言 上一篇,主要介绍了 Vuex 插件的开发,主要涉及以下几个点: Vuex 插件的使用介绍;Vuex 插件开发和使用分析;Vuex 插件机制的分析; 本篇,继续介绍 Vuex 插件机制的实现&…...

图像去噪技术简述

随着每天拍摄的数字图像数量激增,对更准确、更美观的图像的需求也在增加。然而,现代相机拍摄的图像不可避免地会受到噪声的影响,从而导致视觉图像质量下降。因此,需要在不丢失图像特征(边缘、角和其他尖锐结构&#xf…...

数据迁移——技术选型

日常我们在开发中,随着业务需求的变更,重构系统是很常见的事情。重构系统常见的一个场景是变更底层数据模型与存储结构。这种情况下就要对数据进行迁移,从而使业务能正常支行。 背景如下:老系统中使用了mongo数据库,由…...

第二十七章 java并发常见知识内容(CompletableFuture)

JAVA重要知识点CompletableFuture常见函数式编程操作创建 CompletableFuture静态工厂方法处理异步结算的结果异常处理组合 CompletableFuturethenCompose() 和 thenCombine() 区别并行运行多个 CompletableFutureCompletableFuture Java 8 才被引入的一个非常有用的用于异步编…...

Qt扫盲-QMake 使用概述

QMake 使用概述一、概述二、简单开始三、使应用程序可调试1. 添加平台特定的源文件2. 如果文件不存在,停止qmake3. 检查多个条件一、概述 本教程教你qmake的基础知识。qmake 其实就是一个自动化编译的流程控制文件,也是Qt程序的生成makefile的工具&…...

Spring Cloud之Zuul

目录 简介 Zuul中的过滤器 过滤器的执行流程 使用过滤器 route过滤器的默认三种配置 路由到服务 路由到url地址 转发给自己 自定义过滤器 简介 Zuul是Netflix开源的微服务网关,主要功能是路由转发和过滤器,其原理也是一系列filters&#xff0…...

为什么要有分布式锁?

Redis避坑指南:为什么要有分布式锁?作者:京东保险 张江涛1、为什么要有分布式锁?JUC提供的锁机制,可以保证在同一个JVM进程中同一时刻只有一个线程执行操作逻辑;多服务多节点的情况下,就意味着有…...

【Redis】Redis持久化之RDB详解(Redis专栏启动)

📫作者简介:小明java问道之路,2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建工设优化。文章内容兼具广度深度、大厂技术方案,对待技术喜欢推理加验证,就职于知名金融公…...

Retinanet网络与focal loss损失

参考代码:https://github.com/yhenon/pytorch-retinanet 1.损失函数 1)原理 本文一个核心的贡献点就是 focal loss。总损失依然分为两部分,一部分是分类损失,一部分是回归损失。 在讲分类损失之前,我们来回顾一下二…...

Spring事务的失效场景

事务失效场景 方法用private或final修饰 Spring底层使用了AOP,而AOP的实现方式有两种,分别是JDK动态代理和CGLIB,JDK动态代理是实现抽象接口,CGLIB是继承父类,无论哪种方式,都需要重写方法来进行方法增强,而…...

芯动联科在科创板IPO过会:拟募资10亿元,金晓冬为实际控制人

2月13日,上海证券交易所披露的信息显示,安徽芯动联科微系统股份有限公司(下称“芯动联科”)获得科创板上市委会议审议通过。据贝多财经了解,芯动联科于2022年6月24日在科创板递交招股书。 本次冲刺上市,芯…...

数据结构之单链表

一、链表的组成 链表是由一个一个的节点组成的,节点又是一个一个的对象, 相邻的节点之间产生联系,形成一条链表。 例子:假如现在有两个人,A和B,A保存了B的联系方式,这俩人之间就有了联系。 A和…...

儿子跟妈妈关系不好怎么办?这里有解决办法!

15岁的男孩子正处于青春期,很多男孩都傲慢自大,听不进去别人的建议,以自己为中心,认为自己能处理好自己的事情,不想听父母的唠叨。母亲面对青春期的孩子也是举手无措,语气不好,会让孩子更叛逆。…...

论文投稿指南——中文核心期刊推荐(植物保护)

【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…...

华科万维C++章节练习4_6

【程序设计】 题目: 编程输出下列图形,中间一行英文字母由输入得到。 A B B B C C C C C D D D D D D D C C C C C B B B A 开头空一格,字母间空两格…...

详解子网技术

一 : Internet地址 Intemet实质上是把分布在世界各地的各种网络如计算机局域网和广域网、数字数据通信网以及公用电话交换网等互相连接起来而形成的超级网络。但是 , 网络的物理地址给Internet统一全网地址带来两个方面的问题: 第一,物理地址是物理网络技术的一种…...

chatGTP的全称Chat Generative Pre-trained Transformer

chatGPT,有时候我会拼写为:chatGTP,所以知道这个GTP的全称是很有用的。 ChatGPT全名:Chat Generative Pre-trained Transformer ,中文翻译是:聊天生成预训练变压器,所以是GPT,G是生…...

hive数据存储格式

1、Hive存储数据的格式如下: 存储数据格式存储形式TEXTFILE行式存储SEQUENCEFILE行式存储ORC列式存储PARQUET列式存储 2、行式存储和列式存储 解释: 1、上图左面为逻辑表;右面第一个为行式存储,第二个温列式存储; …...

mysql数据库备份与恢复

mysql数据备份: 数据备份方式 物理备份: 冷备:.冷备份指在数据库关闭后,进行备份,适用于所有模式的数据库热备:一般用于保证服务正常不间断运行,用两台机器作为服务机器,一台用于实际数据库操作应用,另外…...

《NFL橄榄球》:辛辛那提猛虎·橄榄1号位

辛辛那提猛虎(英语:Cincinnati Bengals),又译辛辛那提孟加拉虎,是一支职业美式橄榄球球队位于俄亥俄州辛辛那提。他们现时为美联北区的其中一支球队,他们在1968年加入美国橄榄球联合会,并在1970…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...

Python如何给视频添加音频和字幕

在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

OpenLayers 分屏对比(地图联动)

注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...

redis和redission的区别

Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...