【手写 Vuex 源码】第九篇 - Vuex 响应式数据和缓存的实现

news2024/12/23 16:38:06

一,前言

上一篇,主要介绍了 Vuex 的 State 状态安装,主要涉及以下几个点:

  • State 状态的安装逻辑;
  • 两个核心问题的思路;
  • 代码实现以及执行情况分析;

本篇,继续介绍 Vuex 模块相关概念:Vuex 响应式数据和缓存的实现;


二,前文梳理

  • 第六篇,完成了 Vuex 的模块收集;
  • 第七篇、第八篇,两篇完成了 Vuex 的模块安装;

下一步,实现 Vuex 响应式数据和缓存;


三,Vuex 的响应式实现原理

前面,简易的单层 Vuex 实现中,通过借助 Vue 实例与数据代理实现了响应式数据:

export class Store {
  constructor(options) {
    // 获取 options 选项中的 state 对象
    const state = options.state;
    // 获取 options 选项中的 getters 对象:内部包含多个方法
    const getters = options.getters;
    
    // 声明 store 实例中的 getters 对象
    this.getters = {};
    // 将 options.getters 中的方法定义到计算属性中
    const computed = {}

    // 页面通过“{{this.$store.getters.getPrice}}”取值,取的是 getters 对象中的属性
    // 所以,需要将将用户传入的 options.getters 属性中的方法,转变成为 store 实例中的 getters 对象上对应的属性
    Object.keys(getters).forEach(key => {
      // 将 options.getters 中定义的方法,放入计算属性 computed 中,即定义在 Vue 的实例 _vm 上
      computed[key] = () => {
        return getters[key](this.state)
      }

      // 将 options.getters 中定义的方法,绑定到 store 实例中的 getters 对象
      Object.defineProperty(this.getters, key, {
        // 旧:// 取值操作时,执行 options 中对应的 getters 方法(添加computed后废弃,使用逻辑)
        // get: () => options.getters[key](this.state)
        // 新:取值操作时,执行计算属性逻辑
        get: () => this._vm[key]
      })
    });

    // 响应式数据:new Vue({data})
    this._vm = new Vue({
      data: {
        // 在 data 中,默认不会将以$开头的属性挂载到 vm 上
        $$state: state // $$state 对象将通过 defineProperty 进行属性劫持
      },
      computed // 将 options.getters 定义到 computed 实现数据缓存
    })
  }

全部 getters 的安装结果:_wrappedGetters
全部 state 的安装结果:state;

所以,使用 _wrappedGettersstate 创建 Vue 实例,将 State 状态和 getters 都定义在当前 vm 实例上;

创建 resetStoreVM 方法:重置 Store 容器对象的 vm 实例:

  • 传入 this:this 中包含 _wrapperGetters 全部的 getter 方法;
  • 传入 state:rootState 根模块状态,包含全部模块的状态;

借助 Vue 的响应式,为 store 实例上添加一个 Vue 实例_vm,通过计算属性实现 _wrapperGetters 中的 getters 的缓存效果,并将 state 定义到 data 中实现状态的响应式能力;


四,响应式实现核心方法:resetStoreVM

根据以上分析,借助 Vue 中 data 响应式能力与,computed计算属性提供的缓存能力,实现 Vuex 数据响应式及缓存效果;

在 src/vuex/store.js 中,创建 resetStoreVM 方法:

// src/vuex/store.js#resetStoreVM

/**
 * 重置 Store 容器对象的 vm 实例
 * @param {*} store store实例,包含 _wrappedGetters 即全部的 getter 方法;
 * @param {*} state 根状态,在状态安装完成后包含全部模块状态;
 */
function resetStoreVM(store, state) {
  const computed = {}; // 定义 computed 计算属性
  store.getters = {};  // 定义 store 容器实例中的 getters
  // 遍历 _wrappedGetters 构建 computed 对象并进行数据代理
  forEachValue(store._wrappedGetters, (fn, key) => {
    // 构建 computed 对象,后面借助 Vue 计算属性实现数据缓存
    computed[key] = () => {
      return fn();
    }
    // 数据代理:将 getter 的取值代理到 vm 实例上,到计算数据取值
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key]
    });
  })
  // 使用 state 根状态 和 computed 创建 vm 实例,成为响应式数据
  store._vm = new Vue({
    // 借助 data 使根状态 state 成为响应式数据
    data: {
      $$state: state
    },
    // 借助 computed 计算属性实现数据缓存
    computed 
  });
}

备注:

  • 计算属性的所有属性,都会被放到 vm 实例上,所以可以通过store._vm[key]取到;
  • 当取值时,才会走get: () => store._vm[key];此时,store._vm = new Vue已经执行完成;

调用 resetStoreVM 实现 Vuex 状态响应式及 getter 缓存效果:

export class Store {
  constructor(options) {
    const state = options.state;
    this._actions = {};
    this._mutations = {};
    this._wrappedGetters = {};

    // 1,模块收集:options 格式化 -> Vuex 模块树
    this._modules = new ModuleCollection(options);
    console.log("格式化后的模块树对象", this._modules);
    
    // 2,模块安装:
    installModule(this, state, [], this._modules.root);
    
    // 3,将 state 状态、getters 定义在当前的 vm 实例上
    resetStoreVM(this, state);
  }
  // 对外提供属性访问器:当访问 state 时,实际是访问 _vm._data.$$state
  get state() { 
    return this._vm._data.$$state
  }
}
  • new Vuex.Store(options)时,Store构造函数执行模块收集、模块安装、创建 vm 实例;
  • computed 计算属性,也就是 getter,将被挂载到 vm 实例上;state也会被代理到 vm 实例上;
  • 直接访问 state 状态时,会通过属性访问器,相当于直接访问vm实例内部_data.$$state属性;
  • 直接访问store.getters时,会被 getter 代理到store实例中的 _vm 实例上取值;

五,commit 和 dispatch 的处理

视图部分:

// src/App.vue

<template>
  <div id="app">
    商品数量: {{this.$store.state.num}} 个<br>
    商品单价: 10 元<br>
    订单金额: {{this.$store.getters.getPrice}} 元<br>
    <button @click="$store.commit('changeNum',5)">同步更新:数量+5</button>
    <button @click="$store.dispatch('changeNum',-5)">异步更新:数量-5</button>
  </div>
</template>

当点击 commit 和 dispatch 时,会进入 store 类中 commit 和 dispatch 方法:

export class Store {
  constructor(options) {
    const state = options.state;
    this._actions = {};
    this._mutations = {};
    this._wrappedGetters = {};

    // 1,模块收集:options 格式化 -> Vuex 模块树
    this._modules = new ModuleCollection(options);
    // 2,模块安装:
    installModule(this, state, [], this._modules.root);
    // 3,将 state 状态、getters 定义在当前的 vm 实例上
    resetStoreVM(this, state);
  }
  get state() { 
    return this._vm._data.$$state
  }
  /**
   * 通过 type 找到 store 实例的 mutations 对象中对应的方法,并执行
   *    用户可能会解构使用{ commit }, 也有可能在页面使用 $store.commit,
   *    所以,在实际执行时,this 是不确定的,{ commit } 写法 this 为空,
   *    使用箭头函数:确保 this 指向 store 实例;
   * @param {*} type mutation 方法名
   * @param {*} payload 载荷:值或对象
   */
  commit = (type, payload) => {
    // 旧:执行 mutations 对象中对应的方法,并传入 payload 执行
    // this.mutations[type](payload)
    // 新:不再去 mutations 对象中查找,直接在 _mutations 中找到 type 对应的数组,依次执行
    this._mutations[type].forEach(mutation=>mutation.call(this, payload))
  }
  /**
   * 通过 type 找到 store 实例的 actions 对象中对应的方法,并执行
   *    用户可能会解构使用{ dispatch }, 也有可能在页面使用 $store.dispatch,
   *    所以,在实际执行时,this 是不确定的,{ dispatch } 写法 this 为空,
   *    使用箭头函数:确保 this 指向 store 实例;
   * @param {*} type action 方法名
   * @param {*} payload 载荷:值或对象
   */
  dispatch = (type, payload) => {
    // 旧:执行 actions 对象中对应的方法,并传入 payload 执行
    // this.actions[type](payload)
    // 新:不再去 actions 对象中查找,直接在 _actions 中找到 type 对应的数组,依次执行
    this._actions[type].forEach(action=>action.call(this, payload))
  }
}

通过类型,找到 commit、dispatch 对应的数组(数组中存放所有待执行的方法),循环执行即可;

这里使用到了发布订阅模式,先将所有模块中的 mutation 和 action 放到两个数组中,调用时到当前对象中找到对应的数组并依次执行

这样,数据是如何放入,如何执行的就清晰了,后续进行命名空间的处理;

测试效果:

image.png

当点击同步、异步更新时,进入 commit、dispatch 方法,通过 type 类型找到对应数组并依次执行;

备注:虽然 Vuex 模块的定义中 namespaced 命名空间是启用的,但当前代码还尚未支持;


六,结尾

本篇,主要介绍了 Vuex 响应式数据和缓存的实现,主要涉及以下几个点:

  • Vuex 的响应式实现原理;
  • 响应式核心方法 resetStoreVM;
  • commit 和 dispatch 的处理;

下一篇,继续介绍 Vuex 命名空间 namespaced 的实现;

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

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

相关文章

计算机组成原理(五)

3.理解主存储器与CPU的连接原理&#xff1b;   主存通过数据总线、地址总线和控制总线与CPU连接&#xff1b;   数据总线的位数与工作频率的乘积正比于数据传输率&#xff1b;   地址总线的位数决定了可寻址的最大内存空间&#xff1b;   控制总线&#xff08;读/写&am…

excel操作实例:如何制作一个学习计划阶段图表

预则立不预则废。不管是为了避免被裁员的窘迫&#xff0c;还是为了获得更高的薪水&#xff0c;都有必要在2023年尽快学精Excel&#xff0c;成为行业高手。教程把要成为Excel高手的几道坎&#xff0c;也是要攻克的目标做成图表&#xff0c;希望大家的努力更有效。要变成Excel高手…

Redis 缓存穿透、缓存击穿和缓存雪崩

Redis 缓存穿透和缓存雪崩 缓存穿透和缓存雪崩这两个概念和知识点我们一定要掌握&#xff0c;因为我们工作中经常会遇到缓存穿透和缓存雪崩的情况。 Redis 缓存穿透&#xff08;查不到&#xff09; 缓存穿透是指客户端请求一个缓存和数据库中都不存在的 key。由于缓存中不存在…

【遇见青山】项目难点:集群下的分布式锁问题

【遇见青山】项目难点&#xff1a;集群下的分布式锁问题1.问题简介2.分布式锁分析3.基于Redis实现分布式锁1.0版本4.基于Redis实现分布式锁2.0版本&#xff0c;解决锁误删问题5.基于Redis实现分布式锁3.0版本&#xff0c;解决锁的原子性问题1.问题简介 在《【遇见青山】项目难…

Sliver取代Cobalt Strike成黑客渗透工具“新宠”

8月25日消息&#xff0c;攻击者逐渐弃用Cobalt Strike渗透测试套件&#xff0c;转而使用不太知名的类似框架。开源跨平台工具Sliver正取代Brute Ratel成为受攻击者青睐的武器。 在过去的几年里&#xff0c;Cobal Strike被各类攻击者滥用&#xff08;包括勒索软件操作&#xff…

行为型模式 - 模板方法模式Template Method

学习而来&#xff0c;代码是自己敲的。也有些自己的理解在里边&#xff0c;有问题希望大家指出。 模式的定义与特点 模板方法&#xff08;Template Method&#xff09;&#xff0c;模式的定义如下&#xff1a;定义一个操作中的算法骨架&#xff0c;而将算法的一些步骤延迟到子类…

JavaSE XML解析技术的使用详解

文章目录XML解析技术XML解析技术介绍Dom4j解析XML文件Dom4j解析各个节点Dom4j解析案例实战XML解析技术 XML解析技术介绍 XML的数据作用是什么? 最终需要怎样处理? 作用: 存储数据、做配置信息、进行数据传输。 最终需要被程序进行读取&#xff0c;解析里面的信息。 XML解析…

【路径规划】基于A*算法和Dijkstra算法的路径规划(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Python蓝桥杯训练:基本数据结构 [链表]

Python蓝桥杯训练&#xff1a;基本数据结构 [链表] 文章目录Python蓝桥杯训练&#xff1a;基本数据结构 [链表]一、链表理论基础知识二、有关链表的一些常见操作三、力扣上面一些有关链表的题目练习1、[移除链表元素](https://leetcode.cn/problems/remove-linked-list-element…

TCP报头详解及TCP十种核心机制(一)

目录 前言&#xff1a; TCP报头 TCP核心机制 一、确认应答 二、超时重传 小结&#xff1a; 前言&#xff1a; 这篇文章详细介绍了TCP报头中的一些核心数据&#xff0c;及两种TCP核心机制。其他的一些机制会在后面文章中详细介绍。 TCP报头 解释&#xff1a; 1&#xff…

电商仓储与配送云仓是什么?

仓库是整个供给链的关键局部。它们是产品暂停和触摸的点&#xff0c;耗费空间和时间(工时)。空间和时间反过来也是费用。经过开发数学和计算机模型来微调仓库的规划和操作&#xff0c;经理能够显著降低与产品分销相关的劳动力本钱&#xff0c;进步仓库空间应用率&#xff0c;并…

docker/docker-compose 安装mysql5.7

目录使用docker安装mysql5.7docker普通安装docker生产环境安装使用docker-compose 安装注意注意一:docker-compose权限问题注意二:docker pull 找不到镜像使用docker安装mysql5.7 docker普通安装 docker pull mysql:5.7 # 启动容器 docker run -p 3306:3306 --name mysql -e …

数组和对象的拷贝(复制)

复制必须要产生新的对象。以下代码不是复制。 const arr ["孙悟空", "猪八戒", "沙和尚"]const arr2 arr // 不是复制&#xff0c;只是将arr的值赋给arr2&#xff0c;他们指的还是一个对象console.log(arr) // 二者输出一样 console.log(…

数楼梯(加强版)

数楼梯(加强版) 题目背景: 小明一天放学回家,看到从1楼到2楼共有n个台阶,因为好奇,他想尝试一下总共有几种方案到二楼?他可以1步,2步,3步的跳,不能跳3步以上. 他试了很多次都没有解决这个问题,于是请求聪明的你帮忙解决这个问题. 题目描述: 1楼到2楼楼梯有n级台阶。小明每…

Learning C++ No.8【内存管理】

引言&#xff1a; 北京时间&#xff1a;2023/2/12/18:04&#xff0c;昨天下午到达学校&#xff0c;摆烂到现在&#xff0c;该睡睡&#xff0c;该吃吃&#xff0c;该玩玩&#xff0c;在一顿操作之下&#xff0c;目前作息调整好了一些&#xff0c;在此记录&#xff0c;2月11&…

C++基础(6) - 复合类型(下)

文章目录指针1、指针概述1.1 存储器和存储地址空间1.2 内存地址1.3 指针和指针变量2、声明和初始化指针变量2.1 指针变量的声明2.2 指针变量的初始化3、使用指针变量3.1 解除引用3.2 野指针和空指针4、指针的宽度和跨度4.1 自身类型和指向类型4.2 指针变量所取内容的宽度4.3 指…

chatGPT会是银弹吗

chatGP最近火的一塌糊涂&#xff0c;它通过语言生成技术和自然语言处理能力&#xff0c;帮助用户快速解决问题并生成内容。目前&#xff0c;这款工具现在已经拥有超过一亿的活跃用户&#xff0c;并且因其高效率和易用性而受到了广大用户的好评。 不过谷歌可就倒霉了&#xff0c…

Shells:一款功能强大的反向Shell快速生成工具

关于Shells Shells是一款功能强大的反向Shell快速生成工具&#xff0c;该工具由4ndr34z负责开发和维护&#xff0c;可以帮助广大研究人员轻松生成常用的反向Shell。如果你需要一种简单的方法来生成格式化的PowerShell以及Python反向Shell的话&#xff0c;Shells这款工具将是你…

【IPD】敏捷开发与IPD结合的实践培训课程「3月11-12日」

课程名称敏捷开发与 IPD结合的实践 (Agile Development - IPD and Agile Development Practice &#xff09;参加对象企业总工、技术总监、系统架构师、研发经理、测试经理、质量/品质经理、研发测试骨干&#xff0c;以及研发测试技术人员。课程背景软件系统的日益复杂化和用户…

C语言学习笔记-内存管理

这篇将讲解 C 中的动态内存管理。C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。 序号函数和描述1void calloc(int num, int size);在内存中动态地分配 num 个长度为 size 的连续空间&#xff0c;并将每一个字节都初始化为 0。所以…