vuex实现原理

article/2025/4/22 10:35:45

文章目录

    • vuex是什么?
    • 为什么会出现?
    • 怎么使用?
    • 怎么实现?
      • 1、给每个实例注入$store
      • 2、设置state响应数据
      • 3、getters
      • 4、mutations
      • 5、actions
      • 6、modules
      • 7、持久化插件plugins
      • 8、辅助函数mapState
        • 用法
        • 命名空间用法
        • 实现原理
        • 命名空间原理

vuex是什么?

vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理所有组件的状态,并以相应的规则保证状态。

为什么会出现?

为什么出现:vue一般是单项数据流,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
多个视图依赖于同一状态、来自不同视图的行为需要变更同一状态。
作用:多个组件共享数据或者是跨组件传递数据

怎么使用?

// store.js
1.引入Vuex插件;
Vue.use(Vuex);2.将Vuex.Store这个类实例化,并传入一些配置
const store = new Vuex.Store({state:{count:0},mutations:{increment(state){state.count++;},del(state){state.count--;},},actions:{asyncAdd({commit}){setTimeout(() => {commit("increment");}, 2000);}}
})3.将store的实例配置给Vue
// main.js
new Vue({store,render: h => h(App),
}).$mount('#app')4.组件中使用时
// App.vue
add(){this.$store.commit('increment');
},
asyncAdd(){this.$store.dispatch('asyncAdd');
}</script>

怎么实现?

1、vuex的核心api:
install函数:用来注册插件到vue里(说白了就是在vue中执行这个函数,并把vue当作参数传入此函数,使用vue的方法和绑定store到各个组件上)
store类:state、getters、mutations、actions、modules、plugins
辅助函数:mapState、mapActions、mapMutations

围绕这些问题实现
1、怎么让每个vue组件都能拿到$store?
2、怎么实现state数据响应式?
3、getters怎么实现?
4、commit怎么去触发mutation,dispatch怎么触发actions?
5、plugins是怎么实现?
6、mapState怎么实现?

1、给每个实例注入$store

let Vue
const install = (_Vue) => { Vue = _Vue// 使用vue的混入方法,在创建之前,给每个组件都增加$store属性Vue.mixin({// 创建之前会被执行beforeCreate () {// 根实例有store属性if (this.$options && this.$options.store) {this.$store = this.$options.store  } else {// 根实例上没有的store属性,往父亲节点找// new Vue({store}) 这里已经在根组件挂载有store属性this.$store = this.$parent && this.$parent.$store }}})
}
export default {install // 给用户提供一个install方法,默认会被调用
}

在install方法里面,用vue.mixin混入,在beforeCreate的生命周期的钩子函数,使得当每个组件实例化的时候都会调用这个函数,给自己赋值一个store属性

2、设置state响应数据

class Store {constructor (options) {// this.vm  = options.state   只是单纯获取state数据,但是数据修改不会更新界面/** 借用Vue的双向绑定机制让Vuex中data变化实时更新界面 */this.vm = new _Vue({data: {state: options.state}})}
/* 类的属性访问器访问state对象时候,就直接返回响应式的数据Object.defineProperty get 同理*/get state () {return this.vm.state}
}

利用vue的响应式原理,让state的修改都可以更新回视图,而不是单纯获取state数据

3、getters

getters从根本上就是computed,给你返回一些派生的状态(对数据进行过滤操作)

// 简化代码,封装遍历方法
const forEach = (obj, callback) => {Object.keys(obj).forEach((key) => {callback(key, obj[key])})
}
forEach(getters, (getterName, fn) => {Object.defineProperty(store.getters, getterName, {get () {// 让getter执行自己的状态 传入return fn(state)}})
})

在这里插入图片描述
遍历用户传入的参数获取属性名,利用Object.defineProperty的get获取方法执行的结果,赋值到getters对象对应的属性名上,用户通过this.getters.myName就可以调用对应的值

4、mutations

特点:
1)不能直接改变 store 中的状态。改变 store 中的状态的唯一方法是提交 (commit) mutation。
2)每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。调用 store.commit(type, payload) 方法来触发mutations中的相关方法。

forEach(mutations, (mutationName, fn) => {store.mutations[mutationName] || (store.mutations[mutationName] = [])store.mutations[mutationName].push((payload) => { // 先把用户传入的mutations参数的属性和方法保存到store实例上的this.mutations对象里面fn(state, payload)  // 参数是state数据})})// 用户通过this.$store.commit('syncAdd', 10) 传入属性名和荷载,找到对应的函数,遍历执行
commit = (type, payload) => {this.mutations[type].forEach(fn => fn(payload))
}

在这里插入图片描述

5、actions

actions和mutations的区别:
①action提交的是mutation,而不是直接变更状态
②actions用于处理一些异步事件,而mutations一般用于处理同步事件
③通过store.dispatch触发action,参数是vuex.store实例(因为modules需要获取上下文)
通过store.commit触发mutation,参数是state,payload
actions也可以实现同步函数,但是vuex要求必须遵从原则

forEach(actions, (actionName, fn) => {store.actions[actionName] || (store.actions[actionName] = [])store.actions[actionName].push((payload) => {fn(store, payload)  // 参数是vuex.store实例})
})// 用户通过this.$store.dispatch('syncAdd', 10) 传入属性名和荷载,找到对应的函数,遍历执行
dispatch = (type, payload) => {this.actions[type].forEach(fn => fn(payload))
}

在这里插入图片描述
跟mutations差不多,只是传入的参数不一样,需要注意的点

6、modules

打印出来,我们可以看到store挂载一个module集合对象

import Vuex from 'vuex'
mounted () {console.log(this.$store._modules) // ModuleCollection {root: Module}
},

格式化成我们需要的:

let root = {_raw: options,_children: {a: {_raw: {},_children: {},state: { a: 1 }},b: {}},state: options.state
}

在这里插入图片描述
1)通过new ModuleCollection()实例,格式化传入的参数:主要通过遍历注册子模块
2)通过installModule(),递归安装每个模块,把每个模块的state、getters等数据都挂载到根state、getters上

class ModuleCollection {constructor (rootModule) {// 注册模块this.register([], rootModule)}register (path, rootModule) { // 将模块格式化let newModule = {_raw: rootModule,_children: rootModule.modules,state: rootModule.state}if (path.length === 0) { // 如果是根模块,将这个模块挂载到根实例上this.root = newModule} else {// 递归调用reduce方法 [a] 找出c的父级,再挂上去let parent = path.slice(0, -1).reduce((pre, cur) => { // pre 初始值  cur 当前元素return pre._children[cur] // 递归把children挂载在父级别的_children属性下}, this.root)parent._children[path[path.length - 1]] = newModule}// 遍历注册子模块// console.log(rootModule.modules) // {a: {…}, b: {…}}    {c: {…}}    {d: {…}}if (rootModule.modules) { // 如果有modules 开始重新再次注册forEach(rootModule.modules, (moduleName, value) => {this.register(path.concat(moduleName), value) // 循环两次,第一次[a],第二次[b] ,而不是[a,b]})}}
}/** 安装模块 */
const installModule = (store, state, path, rootModule) => {console.log(state)if (path.length > 0) {let parent = path.slice(0, -1).reduce((pre, cur) => {return pre[cur]}, store.state)/** 利用Vue set方法实现数据绑定 */// vue不能在对象上增加不存在的属性,否则不会导致视图更新,要用set方法实现数据绑定_Vue.set(parent, path[path.length - 1], rootModule.state)}let getters = rootModule._raw.gettersif (getters) {forEach(getters, (getterName, fn) => {Object.defineProperty(store.getters, getterName, {get () {// 让getter执行自己的状态 传入return fn(state)}})})}let mutations = rootModule._raw.mutationsif (mutations) {forEach(mutations, (mutationName, fn) => {store.mutations[mutationName] || (store.mutations[mutationName] = [])store.mutations[mutationName].push((payload) => {fn(state, payload)})})}let actions = rootModule._raw.actionsif (actions) {forEach(actions, (actionName, fn) => {store.actions[actionName] || (store.actions[actionName] = [])store.actions[actionName].push((payload) => {fn(store, payload)})})}// 挂载儿子if (rootModule._children) {forEach(rootModule._children, (moduleName, module) => {installModule(store, module.state, path.concat(moduleName), module)})}
}
class Store {constructor (options) {// 把数据格式化我们想要的树this.modules = new ModuleCollection(this._options) /**递归安装模块,把每个模块的的state、getters、mutations、actions,都挂载到根state、getters、mutations、actions对象上this 整个storethis.state 当前的根状态[] 为了递归来创建的  等下要递归把每个state都放到根上this._modules.root 从根模块开始安装*/installModule(this, this.state, [], this.modules.root)}
}

7、持久化插件plugins

作用是:把state都存储在localStorage里面,刷新不会丢失数据
原理:发布订阅模式
实例store的时候,遍历plugins里面的函数,并执行 this.subscribe() 订阅到sote._subscribe数组上
当监测到mutation有变化的时候,依次执行所有的订阅
使用:

// store.js
const persits = (store) => {store.subscribe((mutation, state) => {localStorage.setItem('vuex-state', JSON.stringify(state))})
}
export default new Vuex.Store({ // 导出一个store实例plugins: [persits // 发布,通知所有的订阅]
})
/** 安装模块 */
const installModule = (store, state, path, rootModule) => {let mutations = rootModule._raw.mutationsif (mutations) {forEach(mutations, (mutationName, fn) => {store.mutations[mutationName] || (store.mutations[mutationName] = [])store.mutations[mutationName].push((payload) => {fn(state, payload)console.log(state)// 发布 让所有订阅依次执行store._subscribes.forEach(fn => fn({ type: mutationName, payload }, store.state))})})}
}class Store {constructor (options) {// 将用户的状态放到store中// this.state = options.state/** 借用Vue的双向绑定机制让Vuex中data变化实时更新界面 */this.vm = new _Vue({data: {state: options.state}})// 只循环一次,现在需要把子modules里面的getters、mutations、actions都放到对应的对象里/** 保存一份到本身实例 */this._options = optionsthis._subscribes=[]// 实例store的时候,遍历plugins里面的函数,并执行 this.subscribe() 订阅options.plugins.forEach(plugin => plugin(this))}subscribe (fn) {this._subscribes.push(fn) // 订阅}
}

8、辅助函数mapState

用法

抽象形容:mapState是state的语法糖

import { mapState } from 'vuex';// computed只有mapState的情况下computed: mapState({counts: 'counts', // 第一种写法add: (state) => this.str + ':' + state.add, // 第二种写法
})// 还有其他的情况下
computed: {/*...mapState({counts: state => state.counts,add: state => state.add})*/...mapState([  // 第三种写法:通过数组来赋值'counts','add'])
},

…mapState相当于解构赋值给computed,浅拷贝

let mapState = {name: 'ccc',age: 1,child:{count:2}
}let computed = {...mapState}
computed.age = 18
computed.child.count = 3
console.log(computed) // {name: "ccc", age: 18, child :{count:3}}
console.log(mapState) // {name: "ccc", age: 1, child :{count:3}}

命名空间用法

namespaced:vuex中的store分模块管理,需要在store的index.js中引入各个模块,为了解决不同模块命名冲突的问题,将不同模块的namespaced:true,之后在不同页面中引入getter、actions、mutations时,需要加上所属的模块名
当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:

// store.js
const moduleE = {namespaced: true,state: {name: 'xiaoming',age: 1}
}
export default new Vuex.Store({modules: { // 将模块挂载到根storemoduleE, // 等同于moduleE : 等同于moduleE, 上面模块的命名空间是moduleE// eee: moduleE, // 下面模块的命名空间是 eee}
});
// 带命名空间的绑定函数
computed: {// ...mapState('命名空间名', ["name"])   在辅助函数mapState的第一参数上,填写上模块的命名空间名// ...mapState('moduleE', {//   name: 'name'
// })...mapState('moduleE', ['name'])
}

实现原理

 computed: {// ...mapState('moduleE', { // 命名空间名用法1//   name: 'name'// })// ...mapState('moduleE', ['name']) // 命名空间名用法2// ...mapState({ // 用法1//   age: state => state.age// })...mapState([ // 用法2'age'])},

1)…mapState([ ‘age’]) 会执行一个函数,返回一个对象,通过…解构到computed上
2)执行函数时会判断传入的是字符串,还是对象或数组?
① 如果是对象或数组,都去根实例的state上找(所有module.state都挂载在store.state上)
对象{ age: state => state.age }:执行函数并传入根state作为参数,让它返回对应value
数组[ ‘age’ ]:通过key找到根state上的对应的value
② 如果是字符串,说明是用命名空间来获取值,则通过第一个参数(命名空间名)去根实例store._modulesNamespaceMap上找到对应的module模块,再通过第二个参数(key)找到state上对应的value返回
总结:都是通过key值在state上找到value值,组装成对象返回,然后再解构赋值到computed上

命名空间原理

命名空间原理:
1)安装每一个模块的时候,判断有没有namespaced,为否时,则给他设置false,
为true则找到moduleName和对应module,挂载到根_modulesNamespaceMap={}对象上
2)当通过mapState取值的时候就可以通过命名空间名到根_modulesNamespaceMap上找到对应的值
在这里插入图片描述
代码参考:githunb


http://chatgpt.dhexx.cn/article/GUXQf85P.shtml

相关文章

Vuex的实现原理解析(最清晰)

Vuex的实现原理解析(最清晰) Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,可以帮助我们管理共享状态。 如何在Vue中使用Vuex? 如下先来回顾一下使用Vuex的正确姿势&#xff1a; 引入Vuex插件&#xff1b; // store.js Vue.use(Vuex);将Vuex.Store这个类实例化&…

通俗理解vuex原理---通过vue例子类比

主要通过简单的理解来解释下vuex的基本流程&#xff0c;而这也是vuex难点之一。 首先我们先了解下vuex的作用 vuex其实是集中的数据管理仓库&#xff0c;相当于数据库mongoDB&#xff0c;MySQL等&#xff0c;任何组件都可以存取仓库中的数据。 vuex流程与vue类比 我们看一下…

遇见面试--vuex原理

遇见面试 Vuex原理解析 一、前言 自从学习了VUE框架&#xff0c;其中必不可少的会用到vuex这个核心插件&#xff0c;而且在做项目的时候&#xff0c;基本都会使用&#xff0c;可能你会使用vuex状态管理&#xff0c;但是对vuex原理存在着或多或少的的疑惑或不解&#xff0c;这…

vuex的工作原理

Vuex是做什么的? 官方解释&#xff1a;Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。 它采用 集中式存储管理 应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension&#xff0…

vuex 的实现原理

一、Vuex是什么   Vuex是专门为Vuejs应用程序设计的状态管理工具。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 1、Vuex的构成 由上图&#xff0c;我们可以看出Vuex有以下几个部分构成&#xff1a; 1&#x…

vuex原理

目录 一、 为啥会出现vuex 二 、什么是vuex 三、 怎么使用vuex 四、vue和vuex比较 五、vuex的五个属性 5.1 state 5.2 mutations 5.3 getters 5.4 Actions 5.5 Modules 六、为啥state里的数据可以实现响应式 七、为啥每个组件都可以拿到state里的数据 一、 为啥会出…

vuex的原理

1.vuex的作用 vuex是专门为Vuejs应用程序设计的状态管理工具。其实是集中的数据管理仓库。相当于数据库mongoDB等&#xff0c;任何组件都可以存取仓库中的数据。 vuex的组成部分&#xff1a; state:是存储的基本数据。mutations:提交更改数据。getter:对state加工&#xff0c…

vuex 的基本用法

一、vuex是什么&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c; 这个状态管理应用包含以下几个部分&#xff1a; state&#xff0c;驱动应用的数据源&#xff1b;view&#xff0c;以声明方式将 stat…

DNS风险分析及安全防护研究(三):DNS缓存投毒及防御策略

在前面章节中&#xff0c;我们简单介绍了DNS系统在协议、软件以及结构中脆弱性&#xff0c;并对DNSSEC协议、去中心化结构等安全增强进行了讨论&#xff0c;接下来针对DNS安全所面临的外部攻击威胁和相应的防御策略做下讨论。 1.DNS缓存投毒攻击 在目前各种DNS攻击手段中&…

细说SQL注入:攻击手法与防御策略

一、 SQL注入的基本概念 &#xff08;一&#xff09;什么是SQL注入 SQL注入是一种常见的网络攻击技术&#xff0c;它允许攻击者在Web应用程序中插入并执行恶意的SQL代码。这种攻击通常针对的是数据驱动的应用程序&#xff0c;尤其是那些未对用户输入进行充分过滤和处理的应用程…

服务器被cc攻击的简单防御策略(附代码)

CC攻击&#xff08;也称为网络层攻击或流量攻击&#xff09;是指企图通过向网站或服务器发送大量伪造的请求&#xff0c;以干扰正常的用户访问的攻击。这些请求可能是来自单个设备的&#xff0c;也可能是来自一群被控制的设备的。为了防御CC攻击&#xff0c;你可以考虑使用以下…

DOS防御策略

该方法能解决一些小型服务的DOS防御问题&#xff0c;成本不高&#xff0c;能在一定程度上起到DOS防御的作用。 原本服务架构 我们的服务器向多个客户机提供服务&#xff0c;这里以腾讯云服务器为例&#xff0c;假设服务商租了一台腾讯云的服务器&#xff0c;一旦服务器收到攻击…

网络安全中的常见攻击类型和防御策略

友情链接&#xff1a;简单实用的IPAD协议框架GeWe框架 网络安全是当前互联网世界中的重要问题。本文将详细介绍常见的网络攻击类型&#xff0c;包括分布式拒绝服务攻击&#xff08;DDoS&#xff09;、SQL注入、钓鱼等&#xff0c;并提供相应的防御策略。读者将了解如何识别和应…

会话重放之防御策略、手段

目录 什么是会话重放&#xff1f; 有什么预防手段&#xff1f; 请求添加时间戳或UUID方式实现预防 什么是会话重放&#xff1f; 会话重放是一种攻击方式&#xff0c;攻击者利用先前记录的会话数据来重放或重新发送网络通信流量&#xff0c;以模拟合法用户的身份&#xff0c;…

SQL注入攻击原理及防御策略

一.什么是SQL注入 SQL注入&#xff0c;一般指web应用程序对用户输入数据的合法性没有校验或过滤不严&#xff0c;攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句&#xff0c;在不知情的情况下实现非法操作&#xff0c;以此来实现欺骗数据库服务器执…

安全防御策略trust-untrust-dmz(1)

策略实验 一&#xff0c;规划分析和拓扑图的搭建二&#xff0c;配置思路1&#xff0c;先配置FW000口的IP2&#xff0c;使用IP进入防火墙图形化界面&#xff0c;并且可以创建安全区域3&#xff0c;配置FW100口&#xff0c;其它同理4&#xff0c;配置AR15&#xff0c;配置SERVER2…

浅谈撞库防御策略

2014,12306遭遇撞库攻击,13万数据泄露;2015,乌云网上爆出网易邮箱过亿用户数据由于撞库泄露;数据泄露愈演愈烈,撞库登录成为网站的一大安全威胁,今天小编就和大家探讨一下如何才能够有效的防止撞库攻击。俗语知己知彼,百战不殆,小编在网上找了个撞库教程整理给大家看看…

【UTM使用_入侵防御策略】

UTM使用入侵防御策略 一、网络拓扑图 二、需求描述 外部用户在访问内部的server时启用入侵防御检查&#xff1b;入侵防御策略使用全策略&#xff1b; 三、实验步骤&#xff1a; 配置接口eth1、eth2为路由模式&#xff0c;并设置所需的IP地址 新建IP映射策略&#xff0c;…

网络防御与蓝队实践:探讨网络防御策略、入侵检测系统、安全事件响应等蓝队方面的实际案例和方法

第一章&#xff1a;引言 网络安全一直是当今信息社会中至关重要的话题。随着技术的不断发展&#xff0c;网络威胁也愈发复杂和隐匿。在这样的背景下&#xff0c;网络防御变得尤为重要&#xff0c;蓝队作为网络防御的重要一环&#xff0c;起着至关重要的作用。本文将深入探讨网…