【手写 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;
点击按钮更新 Vuex 中的状态,在对应 mutation 方法执行时,依次调用订阅函数,通知 Vuex 插件状态发生变化:
订阅回调中处理:将当前最新的根状态保存至本地存储:
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.state
或 rootState
旧状态,需要根据模块路径重新获取对应的新状态进行处理;否则页面不会更新;(涉及 mutation、getter、_subscribes回调传参等相关处理逻辑需更新);
4,插件效果测试
- 点击按钮更新状态,视图与本地存储同步变化;
- 页面刷新后,状态持久化成功;
四,结尾
本篇,主要介绍了 Vuex 插件机制的实现,主要涉及以下几个点:
- Vuex 插件机制分析;
- Vuex 插件机制核心逻辑实现:plugins 插件注册、subscribe 订阅收集、replaceState 状态替换;
下一篇,继续介绍 Vuex 辅助函数的实现;
相关文章:
【手写 Vuex 源码】第十二篇 - Vuex 插件机制的实现
一,前言 上一篇,主要介绍了 Vuex 插件的开发,主要涉及以下几个点: Vuex 插件的使用介绍;Vuex 插件开发和使用分析;Vuex 插件机制的分析; 本篇,继续介绍 Vuex 插件机制的实现&…...
图像去噪技术简述
随着每天拍摄的数字图像数量激增,对更准确、更美观的图像的需求也在增加。然而,现代相机拍摄的图像不可避免地会受到噪声的影响,从而导致视觉图像质量下降。因此,需要在不丢失图像特征(边缘、角和其他尖锐结构…...
数据迁移——技术选型
日常我们在开发中,随着业务需求的变更,重构系统是很常见的事情。重构系统常见的一个场景是变更底层数据模型与存储结构。这种情况下就要对数据进行迁移,从而使业务能正常支行。 背景如下:老系统中使用了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࿰…...
为什么要有分布式锁?
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…...
2、线程、块和网格
目录一、线程、块、网格概念二、代码分析2.1 打印第一个线程块的第一线程2.2 打印当前线程块的当前线程2.3 获取当前是第几个线程一、线程、块、网格概念 CUDA的软件架构由网格(Grid)、线程块(Block)和线程(Thread&am…...
C++ 算法主题系列之贪心算法的贪心之术
1. 前言 贪心算法是一种常见算法。是以人性之念的算法,面对众多选择时,总是趋利而行。 因贪心算法以眼前利益为先,故总能保证当前的选择是最好的,但无法时时保证最终的选择是最好的。当然,在局部利益最大化的同时&am…...
请注意,PDF正在传播恶意软件
据Bleeping Computer消息,安全研究人员发现了一种新型的恶意软件传播活动,攻击者通过使用PDF附件夹带恶意的Word文档,从而使用户感染恶意软件。 类似的恶意软件传播方式在以往可不多见。在大多数人的印象中,电子邮件是夹带加载了恶…...
【Kubernetes】【二】环境搭建 环境初始化
本章节主要介绍如何搭建kubernetes的集群环境 环境规划 集群类型 kubernetes集群大体上分为两类:一主多从和多主多从。 一主多从:一台Master节点和多台Node节点,搭建简单,但是有单机故障风险,适合用于测试环境多主…...
Python:每日一题之发现环(DFS)
题目描述 小明的实验室有 N 台电脑,编号 1⋯N。原本这 N 台电脑之间有 N−1 条数据链接相连,恰好构成一个树形网络。在树形网络上,任意两台电脑之间有唯一的路径相连。 不过在最近一次维护网络时,管理员误操作使得某两台电脑之间…...
C++设计模式(14)——享元模式
亦称: 缓存、Cache、Flyweight 意图 享元模式是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。 问题 假如你希望在长时间工作后放…...
SpringCloud之Eureka客户端服务启动报Cannot execute request on any known server解决
项目场景: 在练习SpringCloud时,Eureka客户端(client)出现报错:Cannot execute request on any known server 问题描述 正常启动SpringCloud的Server端和Client端,结果发现Server端的控制台有个Error提示,如下&#…...
从零开始搭建kubernetes集群环境(虚拟机/kubeadm方式)
文章目录1 Kubernetes简介(k8s)2 安装实战2.1 主机安装并初始化2.2 安装docker2.3 安装Kubernetes组件2.4 准备集群镜像2.5 集群初始化2.6 安装flannel网络插件3 部署nginx 测试3.1 创建一个nginx服务3.2 暴漏端口3.3 查看服务3.4 测试服务1 Kubernetes简…...
【零基础入门前端系列】—表格(五)
【零基础入门前端系列】—表格(五) 一、表格 表格在数据展示方面非常简单,并且表现优秀,通过与CSS的结合,可以让数据变得更加美观和整齐。 单元格的特点:同行等高、同列等宽。 表格的基本语法࿱…...
C#开发的OpenRA的只读字典IReadOnlyDictionary实现
C#开发的OpenRA的只读字典IReadOnlyDictionary实现 怎么样实现一个只读字典? 这是一个高级的实现方式,一般情况下,开发人员不会考虑这个问题的。 毕竟代码里,只要小心地使用,还是不会出问题的。 但是如果在一个大型的代码,或者要求比较严格的代码里,就需要考虑这个问题了…...
更换网站域名之后/安徽seo
说起来尴尬,好几年前就搞了github建了仓库,当时玩得还有点6,后来一直的公司都是svn,自己业务项目也没玩,都忘了要怎么提交代码到自己的仓库了。 这边再来一波记录吧。 一、配置用户名 git config --global user.name &…...
手机网站建设市场报价/seo软件优化工具软件
“(关注上方公众号,获取最新IT求职资讯)码工通常所说的"包裹",也就是 offer package, 包括 base,stock,和bonus三个方面。一般来说,base在glassdoor等网站、论坛上都是比较容易找到的,而且&#…...
网络举报网站/网上推广赚钱方法
一、设备信息配置 脚本如下: 1 from appium import webdriver2 3 class my_app():4 def __init__(self):5 desired_caps {}6 # 使用的手机或模拟器操作系统7 desired_caps["platformName"] "Android"8 9 …...
互联网销售可以卖什么产品/中国seo网站
效果图...
大良用户网站建设/新站如何让百度快速收录
目录 一,为什么需要动态内存分配? 1,传统数组的缺点: 2,为什么需要动态内存分配? 二,动态内存函数的介绍 1,malloc和free 2,calloc 3,realloc 三,常见的动态内存错误 1,对N…...
为什么几年前做的网站视频看不了/seo搜索引擎入门教程
杂牌蓝牙在2003系统使用新驱动的破解方法!--------------------------------------------------------------------------------现在最为流行的蓝牙驱动有三个:微星的bt49、任意品牌的1.4.1.6版和最新的1.4.2 Build 10版驱动。本文使用的1.4.1.6版和1.4.…...