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

news2024/11/29 10:38:17

一,前言

上一篇,主要介绍了 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.js

export 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.js

const 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 状态变更事件订阅的处理函数 fn
      store._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.js

import 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.js

import 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.state
        fn(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 辅助函数的实现;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/344774.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

moveToCoordinateF3DconcatenateRotations

moveToCoordinate 演示视频: 注意:前提是3~6轴机器人机构且不是PickAndPlace 该方法_3D。Poses.moveToCoordinate 移动由 指定的对象,该对象 对应于支持的机器人配置之一,只要标识的机器人配置支持,其第一个动画指向指定坐标和指定旋转。这无需您定义姿势即可工作。 工…

Python期末复习知识点大合集(期末不挂科版)

Python期末复习知识点大合集(期末不挂科版) 文章目录Python期末复习知识点大合集(期末不挂科版)一、输入及类型转换二、格式化输出:字符串的format方法三、流程控制四、随机数生成五、字符串六、序列索(含字…

stm32f407探索者开发板(十五)——NVIC中断优先级管理

文章目录零、前言一、NVIC中断优先级分组1.1 中断的管理方法1.2 抢占优先级&相应优先级的区别1.3 举例1.4 特别说明1.5 中断优先级分组函数二、NVIC中断优先级设置2.1 中断设置相关寄存器2.2 中断设置优先级2.2.1 中断优先级控制的寄存器组 IP[240]2.2.2 中断使能寄存器组 …

TCP核心机制之连接管理详解(三次握手,四次挥手)

目录 前言: 建立连接 建立连接主要两个TCP状态: 断开连接 断开连接的两个重要状态 小结: 前言: TCP是如何建立对端连接,如何断开连接,这篇文章会详细介绍。 建立连接 首先明确连接的概念&#xff1a…

Docker系列之九巧用Nginx

前言 Nginx是个很棒的反向代理服务工具,之前我都是直接安装到服务器上去,过程繁琐也许不是难以接受,但是有更简单地Docker镜像服务,我自然没必要再次舍近求远。 一些配置是参考自我以前的文章Docker系列一安装Docker和Hexo历险记…

影片自由,丝滑流畅,Docker容器基于WebDav协议通过Alist挂载(百度网盘/阿里云盘)Python3.10接入

使用过NAS(Network Attached Storage)的朋友都知道,它可以通过局域网将本地硬盘转换为局域网内的“网盘”,简单理解就是搭建自己的“私有云”,但是硬件和网络成本都太高了,有点可望而不可及的意思。Alist开源库则可以满足我们&…

Docker+Nginx+KeepaLived实现Nginx一主一从高可用

系统版本 Centos7 IP:10.10.11.79 Master IP:10.10.11.81 Slave 虚拟ip:10.10.11.77 客户端发起一个请求 ,请求没有到Nginx的实际IP上,而是请求的虚拟IP(会和实际IP通过配置文件进行绑定) 如果有一台Nginx…

Linux进阶(Shell编程学习一)

由于shell脚本在java项目运维方面极其重要,比如服务的启动脚本,日志的分割脚本,文件的管理脚本大多都是shell脚本去实现的。所以作为java开发者懂linux的基本命令,会基本的shell编程是必要的。 Shell 是一个用 C 语言编写的程序&…

nodejs+vue学生考试成绩数据分析与可视化系统vscode

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.3 B/S结构 4 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:技术背景…

springboot 集成driud

druid官方文档导入jar包<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid-spring.version}</version></dependency>配置数据源spring:datasource:url: jdbc:m…

什么是销售管理?销售管理的五大职能

销售管理听起来很简单&#xff0c;似乎只是负责销售并确保客户满意&#xff0c;但事实上&#xff0c;它远不止于此。 销售管理的实际职能包括监督销售团队的工作&#xff0c;制定计划和设定目标&#xff0c;通常还包括确保销售流程的效率以获得最佳业务结果。 什么是销售管理…

千兆网口down/up偶发link成百兆问题

a. 软硬件环境&#xff1a;​NXP LS1043A IGB211&#xff08;pcie接口千兆网卡&#xff09;:linux 4.9.19 igb driver Nvidia Xavier NX RTL8211F: linux4.9​ ​NXP LS1043A -----(pcie)--- IGB211 <---(PCB走线代替网线)-----> RTL8211F&#xff08;phy&#xff09;…

剑指 Offer 56 - II. 数组中数字出现的次数 II

题目 在一个数组 nums 中除一个数字只出现一次之外&#xff0c;其他数字都出现了三次。请找出那个只出现一次的数字。 思路 这题是剑指 Offer 56 - I. 数组中数字出现的次数的变体&#xff0c;本题只有一个数num出现一次&#xff0c;其余的均出现三次 三次的话使用异或消无法…

云计算与 SaaS 有何区别?

云计算与 SaaS 有何区别&#xff1f;众所周知&#xff0c;SaaS是云计算的三种服务模式其中之一。 三种分别是&#xff1a; Iaas&#xff1a;基础设施即服务Paas&#xff1a;平台即服务SaaS&#xff1a;软件即服务 对于三者的区别&#xff0c;一起来吃顿烤肉&#xff0c;听我慢…

13_FreeRTOS消息队列

目录 队列简介 FreeRTOS队列特点 队列操作基本过程 队列结构体介绍 队列结构体整体示意图 队列相关API函数介绍 创建队列相关API函数介绍 往队列写入消息API函数 往队列写入消息函数入口参数解析 从队列读取消息API函数 实验源码 队列简介 队列是任务到任务、任务…

开发微服务电商项目演示(五)

登录方式调整第1步&#xff1a;从zmall-common的pom.xml中移除spring-session-data-redis依赖注意&#xff1a;本章节中不采用spring-session方式&#xff0c;改用redis直接存储用户登录信息&#xff0c;主要是为了方便之后的jmeter压测&#xff1b;2&#xff09;这里只注释调用…

如何使用ArcGIS转换坐标

1.概述大家都知道ArcGIS提供了坐标转换功能&#xff0c;在我们手里的数据坐标系千差万别&#xff0c;经常会遇到转换坐标的时候&#xff0c;那么是否可以用ArcGIS进行转换&#xff1f;答案是肯定的&#xff0c;但是转换的过程比较复杂&#xff0c;这里为大家介绍一下转换的方法…

微软 new Bing 通过 ChatGPT 加持后搜索体验暴增,国内该如何申请使用那?

就在近期微软公布了自家的 Bing 将加持 ChatGPT 推出新版&#xff0c;消息一出 Bing 的下载量直接翻了 10 倍以上&#xff0c;48小时内就已经有 100 万名新用户申请加入了 还可以申请试用 new bing 吗&#xff1f; 目前用户还无法直接访问新版 bing &#xff0c;还是要前往申…

从一次有趣的漏洞分析到一个有趣的PHP后门

起因 事情的起因很有趣&#xff0c;前几天我正对着电脑发呆的时候&#xff0c;突然有个安全交流群的群友来找我交流一个问题 大概的意思就是&#xff0c;他在挖SRC的时候&#xff0c;发现一处资产存在目录遍历漏洞&#xff0c;它通过这个漏洞&#xff0c;找到目标资产使用了一…

基于图像的相机定位:概述

虚拟现实、增强现实、机器人和自动驾驶最近引起了学术界和工业界的广泛关注&#xff0c;其中基于图像的相机定位是一项关键任务。然而&#xff0c;还没有关于基于图像的相机定位的完整评论。迫切需要映射这个主题&#xff0c;使个人能够快速进入该领域。在本文中&#xff0c;概…