Vuex4.0.0 源码解析

news2024/11/15 21:45:23
  • 本文章基于以下版本撰写
    • VUE 版本: 3.0
    • VUEX 版本:4.0.0
    • Vuex仓库:https://github.com/vuejs/vuex/tree/v4.0.0
    • Vux文档:https://vuex.vuejs.org/zh/

在 vue 中使用 vuex

import { createApp } from 'vue'
import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
  state () {
    return {
      count: 0
    }
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

const app = createApp({ /* 根组件 */ })

// 将 store 实例作为插件安装
app.use(store)

从 createStore 引入讲起

让我们先看一下 vuex/src 目录,引入 createStore 就是从 index.js 文件中引入
在这里插入图片描述

// src/index.js

⭐️import { Store, createStore } from './store'
import { storeKey, useStore } from './injectKey'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
import { createLogger } from './plugins/logger'

export default {
  version: '__VERSION__',
  Store,
  storeKey,
  ⭐️createStore,
  useStore,
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers,
  createLogger
}

export {
  Store,
  storeKey,
  createStore,
  useStore,
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers,
  createLogger
}

可以看到 createStore 是从 store 文件中引用,那么下面我们来看下 store 文件

// store.js

export function createStore(options) {
  return new Store(options)
}

直接返回了一个 Store 的实例,所以我们知道 Store 肯定是一个 class,下面我们来看下 Store 类

// store.js
export class Store {
  constructor(options = {}) {
    if (__DEV__) {
      assert(
        typeof Promise !== 'undefined',
        `vuex requires a Promise polyfill in this browser.`
      )
      assert(
        this instanceof Store,
        `store must be called with the new operator.`
      )
    }

    const { plugins = [], strict = false, devtools } = options

    // 存储内部状态
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    ⭐️this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._makeLocalGettersCache = Object.create(null)

    this._scope = null

    this._devtools = devtools

    const store = this
    // 在实例自身身上挂两个方法分别是原型上的dispatch 、commit方法,并将函数内部的this指针强行指向当前创建的store对象。
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch(type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit(type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    this.strict = strict

    const state = this._modules.root.state

    // 初始化根Moudule
    ⭐️⭐️installModule(this, state, [], this._modules.root)

    // 对State进行响应式处理
    ⭐️⭐️⭐️resetStoreState(this, state)

    // 应用插件
    plugins.forEach((plugin) => plugin(this))
  }
  
  ⭐️⭐️⭐️⭐️install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}

我们先看下 constructor 部分,我们知道通过 new 命令生成对象实例时,自动调用该方法。
1. 所以首先是存储内部状态
2. 在实例自身身上挂两个方法分别是原型上的dispatch 、commit方法,并将函数内部的this指针强行指向当前创建的store对象。
3. 初始化根Moudule
4. 对State进行响应式处理
5. 应用插件
6. 全局注册

⭐️ ModuleCollection 模块处理

我们从 this._modules = new ModuleCollection(options) 创建实例对象讲起,从字面意思可知是一个 module 收集的过程

// src/module/module-collection.js

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }

可以看到是调用 register 方法

// src/module/module-collection.js

  register (path, rawModule, runtime = true) {
    if (__DEV__) {
      assertRawModule(path, rawModule)
    }

    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

创建 Module 实例,先挂在到 root 属性上,然后看有没有 modules 属性,有的话就递归,给每个模块都创建一个 Module 实例对象,

// src/module/module.js

export default class Module {
  constructor (rawModule, runtime) {
    this.runtime = runtime
    // Store some children item
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    this._rawModule = rawModule
    const rawState = rawModule.state

    // Store the origin module's state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
  }

这样每个模块就有自己单独的 state,实现独立管理自己模块的状态

⭐️⭐️ 初始化根 Moudle installModule

// src/store-util.js

export function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  ⭐️if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  ⭐️⭐️if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
          )
        }
      }
      parentState[moduleName] = module.state
    })
  }

  ⭐️⭐️⭐️const local = module.context = makeLocalContext(store, namespace, path)

  ⭐️⭐️⭐️⭐️module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  ⭐️⭐️⭐️⭐️module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  ⭐️⭐️⭐️⭐️module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  ⭐️⭐️⭐️⭐️⭐️module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

⭐️:
若module.namespaced = true : 此Module将被加入store._modulesNamespaceMap内,其key为Module嵌套的路径。

⭐️⭐️:
非root Module时:子Module.state注入到父节点的state对象里。

⭐️⭐️⭐️:
对store进行局部化,这里主要对module.namespaced= true 的module进行另外处理,其内部的成员都需要进行namespace路径处理处理。

⭐️⭐️⭐️⭐️:
对 mutation、action、getter 进行封装,放在 Store 对应的 _mutations、_actions、_wrappedGetters 里

⭐️⭐️⭐️⭐️⭐️:
若当前module含有子module时,遍历当前model的_children属性,迭代执行installModule。

⭐️⭐️⭐️对State进行响应式处理 resetStoreState

// src/store-util.js

export function resetStoreState (store, state, hot) {
  const oldState = store._state
  const oldScope = store._scope

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computedObj = {}
  const computedCache = {}

  // create a new effect scope and create computed object inside it to avoid
  // getters (computed) getting destroyed on component unmount.
  const scope = effectScope(true)

  ⭐️scope.run(() => {
    forEachValue(wrappedGetters, (fn, key) => {
      // use computed to leverage its lazy-caching mechanism
      // direct inline function use will lead to closure preserving oldState.
      // using partial to return function with only arguments preserved in closure environment.
      computedObj[key] = partial(fn, store)
      computedCache[key] = computed(() => computedObj[key]())
      Object.defineProperty(store.getters, key, {
        get: () => computedCache[key].value,
        enumerable: true // for local getters
      })
    })
  })

  ⭐️⭐️store._state = reactive({
    data: state
  })

  // register the newly created effect scope to the store so that we can
  // dispose the effects when this method runs again in the future.
  store._scope = scope

  // enable strict mode for new state
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldState) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldState.data = null
      })
    }
  }

  // dispose previously registered effect scope if there is one.
  if (oldScope) {
    oldScope.stop()
  }
}

⭐️:
将 getter 注册为计算属性

⭐️⭐️:
让 state 变为响应式对象

⭐️⭐️⭐️⭐️ Store 全局注册

我们知道当我们应用插件 app.use(store) 时候,会自动调用 install 方法

4.0版本 vuex 使用的是 provide / inject 来实现全局注册,因为 vue3 已经不支持 $ api

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }

除了通过 app.config.globalProperties 设置全局属性$store,还provide了一个storeKey,这显然是为 useStore() 做准备。

// src/injectKey.js

import { inject } from 'vue'

export const storeKey = 'store'

export function useStore (key = null) {
  return inject(key !== null ? key : storeKey)
}

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

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

相关文章

C语言中函数的先后关系,java和C语言的语法区别

以上代码中,sum函数在上面,main函数在下面,在main函数中就可以调用sum函数。 如果sum函数定义在main函数后面,则需要在main函数前面声明sum函数。即在main函数前面写:void sum(int begin, int end); C语言中&#xf…

EMC测试中的电流探头

专栏 专注介绍电磁兼容(EMC)的相关知识,算是对本人浸染 EMC 专业十余年的一个阶段小结 。 本文目录1.EMC测试中的电流探头是什么?2.电流探头的主要指标要求3.什么是转移阻抗?1.EMC测试中的电流探头是什么? …

校园兼职网站

开发工具(eclipse/idea/vscode等): 数据库(sqlite/mysql/sqlserver等): 功能模块(请用文字描述,至少200字): 网站前台:关于我们、联系我们、资讯信息、企业信息、职位信息 管理员模块: 1、管理关于我们、联…

vTESTstudio入门到精通 - vTESTstudio工具栏介绍_File

vTESTstudio作为跟CANoe工具并套使用的一个强大的工具,那么想要用好它就得对他有个全面的认识,第一步起码要知道tade工具栏都包含哪些功能要清楚,今天我们就着重分享下vTESTstudio工具栏包含哪些内容,以便在我们编程的时候更好的去…

java 瑞吉外卖day2 笔记 员工增加 员工信息分页查询

PostMapping public R save(HttpServletRequest request,RequestBody Employee employee){log.info("新增员工:{}",employee);//设置初始密码123456 需要进行md5加密employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));em…

homeassistant 接入小米温湿度计2

方法可能有很多种,但是我只用这种方法介入成功了 环境: - 香橙派(自带蓝牙,树莓派应该也可以) - 小米温湿度计2 - 网站:Telink Flasher v4.7 、 Telink Flasher (atc1441.github.io) - 固件&#xff1…

基于java+springmvc+mybatis+vue+mysql的高校普法系统

项目介绍 随着信息化时代的到来,管理系统都趋向于智能化、系统化,高校普法系统也不例外,但目前国内的市场仍都使用人工管理,市场规模越来越大,同时信息量也越来越庞大,人工管理显然已无法应对时代的变化,而…

堆排序快速排序插入排序

堆排序 数据结构使用的是1 dimension的数组来作为二叉树的堆结构,所以并没有使用结构体,而是直接使用了数组 而且堆是完全二叉树,也就是除了最后一层以外,其他层都是满二叉树,最后一层可能不满,所以1dime…

基于jeecgboot的flowable流程支持服务任务的功能

1、描述 Java服务任务用于调用外部Java类。 2、图形表示法 服务任务可视化为圆角矩形,左上角有一个小齿轮图标,如下图: 3、XML表示 有三种方法声明如何调用Java逻辑,下面分别介绍: 调用固定的类 使用flowable:cla…

Flask打开调试模式

调试模式 Flask支持本地开发,但是每次修改代码后需要手动重新启动程序,这不是很友好,事实上Flask支持调试模式来做到更好,Flask服务会在代码更改时自动重新加载,如果出现问题,还能提供调试器以供调试 如何…

docker登陆MySQL提示密码错误,Navicat也连接不上

问题原因 今天在操作自己云服务器MySQL数据库的时候发现使用root进行远程登录的时候被拒绝了,一直提示Access denied for user ‘root‘‘localhost‘ (using password: YES)。 可以确认自己账号密码是没有错的。后来以为是远程访问出问题了,进入到容器…

JavaSE基础篇:泛型说明

本文整理自B站:JavaSE强化教程泛型,由点到面的讲解了整个泛型体系第一章:泛型概述一:泛型概念二:泛型类1:泛型类使用2:泛型类派生子类1):子类也是泛型类2):子类不是泛型类…

SQL进阶笔记

SQL进阶笔记 CASE表达式 简单case表达式 case sexwhen 1 then 男when 2 then 女else 其他 end搜索case表达式 case when sex 1 then 男when sex 2 then 女else 其他 end编写SQL语句的时候需要注意,在发现为真的WHEN子句时,CASE表达式的真假值判断就会中…

Java并发编程(一)—— FutureTask超详细教程

一、前言 创建线程有几种方式?这个问题的答案应该是可以脱口而出的吧: 继承 Thread 类实现 Runnable 接口但这两种方式创建的线程是属于三无产品: 没有参数没有返回值没办法抛出异常用着三无产品总是有一些弊端,其中没办法拿到返回值是最让人不能忍的,于是 Callable 就诞…

TeamsApp LukcyDraw升级之路 之 DB Infra 篇

今天继续 LuckyDraw 的升级之路,之前在1500以上用户同时使用的时候,特别是在短时间内大家一起点击参与抽奖参的时候,服务会出现大量的错误,分析后发现,出现错误的原因基本都是 Azure Storage Table 返回的。当时使用 A…

【GRU回归预测】基于matlab卷积神经网络结合门控循环单元CNN-GRU数据预测(多输入单输出)【含Matlab期源码 2274期】

⛄一、CNN-GRU数据预测 1 理论基础 1.1 CNN算法 负荷序列数据为一维数据,用一维卷积核对数据进行卷积处理,以获取数据的特征。 现设定卷积核的维度为3,移动步长为1,对输入数据进行卷积,以获得特征图图谱,即…

记录那不用百度的美好代码

努力工作,日益消瘦,总有些代码不想记住。我称之为:拿来即用系列 一:格式化时间 1.去除时间的T // 去除日期中T function timeFormatSeconds(time) {if(!time) return time;var date time.substr(0, 10); //年月日var hours ti…

jsp+ssm计算机毕业设计宠物商城【附源码】

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: JSPSSM mybatis Maven等等组成,B/S模式 Mave…

基于SPRINGBOOT的健康饮食管理系统

11-8开发工具(eclipse/idea/vscode等): 数据库(sqlite/mysql/sqlserver等): 功能模块(请用文字描述,至少200字): 基于springboot框架,通过对事物健康程度的总结与归纳,进行软件设计,并通过优秀的…

go 语言Http服务器远程执行shell命令

背景 想要在服务器上起一个http服务器,用来远程执行shell命令并且返回。 基础版 package mainimport ("io""log""net/http" )func main() {// Hello world, the web serverhelloHandler : func(w http.ResponseWriter, req *http.R…