手写Vuex核心原理,再也不怕面试官问我Vuex原理

article/2025/4/22 11:08:51

手写Vuex核心原理

文章目录

  • 手写Vuex核心原理
    • 一、核心原理
    • 二、基本准备工作
    • 三、剖析Vuex本质
    • 四、分析Vue.use
    • 五、完善install方法
    • 六、实现Vuex的state
    • 七、实现getter
    • 八、实现mutation
    • 九、实现actions

一、核心原理

  1. Vuex本质是一个对象
  2. Vuex对象有两个属性,一个是install方法,一个是Store这个类
  3. install方法的作用是将store这个实例挂载到所有的组件上,注意是同一个store实例。
  4. Store这个类拥有commit,dispatch这些方法,Store类里将用户传入的state包装成data,作为new Vue的参数,从而实现了state 值的响应式。

二、基本准备工作

我们先利用vue-cli建一个项目

删除一些不必要的组建后项目目录暂时如下:

已经把项目放到 github:https://github.com/Sunny-lucking/howToBuildMyVuex 可以卑微地要个star吗。有什么不理解的或者是建议欢迎评论提出

我们主要看下App.vue,main.js,store/index.js

代码如下:

App.vue

<template><div id="app">123</div>
</template>

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {},mutations: {},actions: {},modules: {}
})

main.js

import Vue from 'vue'
import App from './App.vue'
import store from './store'Vue.config.productionTip = falsenew Vue({store,render: h => h(App)
}).$mount('#app')

现在我们启动一下项目。看看项目初始化有没有成功。

ok,没毛病,初始化成功。

现在我们决定创建自己的Vuex,于是创建myVuex.js文件

目前目录如下

再将Vuex引入 改成我们的myVuex

//store/index.js
import Vue from 'vue'
import Vuex from './myVuex' //修改代码Vue.use(Vuex)export default new Vuex.Store({state: {},mutations: {},actions: {},modules: {}
})

三、剖析Vuex本质

先抛出个问题,Vue项目中是怎么引入Vuex。

  1. 安装Vuex,再通过import Vuex from 'vuex'引入
  2. 先 var store = new Vuex.Store({…}),再把store作为参数的一个属性值,new Vue({store})
  3. 通过Vue.use(Vuex) 使得每个组件都可以拥有store实例

从这个引入过程我们可以发现什么?

  1. 我们是通过new Vuex.store({})获得一个store实例,也就是说,我们引入的Vuex中有Store这个类作为Vuex对象的一个属性。因为通过import引入的,实质上就是一个导出一个对象的引用

所以我们可以初步假设

Class Store{}let Vuex = {Store
}
  1. 我们还使用了Vue.use(),而Vue.use的一个原则就是执行对象的install这个方法

所以,我们可以再一步 假设Vuex有有install这个方法。

Class Store{}
let install = function(){}let Vuex = {Store,install
}

到这里,你能大概地将Vuex写出来吗?

很简单,就是将上面的Vuex对象导出,如下就是myVuex.js

//myVuex.js
class Store{}
let install = function(){}let Vuex = {Store,install
}export default Vuex

我们执行下项目,如果没报错,说明我们的假设没毛病。

天啊,没报错。没毛病!

四、分析Vue.use

Vue.use(plugin);

(1)参数

{ Object | Function } plugin

(2)用法

安装Vue.js插件。如果插件是一个对象,必须提供install方法。如果插件是一个函数,它会被作为install方法。调用install方法时,会将Vue作为参数传入。install方法被同一个插件多次调用时,插件也只会被安装一次。

关于如何上开发Vue插件,请看这篇文章,非常简单,不用两分钟就看完:如何开发 Vue 插件?

(3)作用

注册插件,此时只需要调用install方法并将Vue作为参数传入即可。但在细节上有两部分逻辑要处理:

1、插件的类型,可以是install方法,也可以是一个包含install方法的对象。

2、插件只能被安装一次,保证插件列表中不能有重复的插件。

(4)实现

Vue.use = function(plugin){const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));if(installedPlugins.indexOf(plugin)>-1){return this;}<!-- 其他参数 -->const args = toArray(arguments,1);args.unshift(this);if(typeof plugin.install === 'function'){plugin.install.apply(plugin,args);}else if(typeof plugin === 'function'){plugin.apply(null,plugin,args);}installedPlugins.push(plugin);return this;
}

1、在Vue.js上新增了use方法,并接收一个参数plugin。

2、首先判断插件是不是已经别注册过,如果被注册过,则直接终止方法执行,此时只需要使用indexOf方法即可。

3、toArray方法我们在就是将类数组转成真正的数组。使用toArray方法得到arguments。除了第一个参数之外,剩余的所有参数将得到的列表赋值给args,然后将Vue添加到args列表的最前面。这样做的目的是保证install方法被执行时第一个参数是Vue,其余参数是注册插件时传入的参数。

4、由于plugin参数支持对象和函数类型,所以通过判断plugin.install和plugin哪个是函数,即可知用户使用哪种方式祖册的插件,然后执行用户编写的插件并将args作为参数传入。

5、最后,将插件添加到installedPlugins中,保证相同的插件不会反复被注册。(~~让我想起了曾经面试官问我为什么插件不会被重新加载!!!哭唧唧,现在总算明白了)

五、完善install方法

我们前面提到 通过Vue.use(Vuex) 使得每个组件都可以拥有store实例

这是什么意思呢???

来看mian.js

import Vue from 'vue'
import App from './App.vue'
import store from './store'Vue.config.productionTip = false;new Vue({store,render: h => h(App)
}).$mount('#app');

我们可以发现这里只是将store ,也就是store/index.js导出的store实例,作为Vue 参数的一部分。

但是这里就是有一个问题咯,这里的Vue 是根组件啊。也就是说目前只有根组件有这个store值,而其他组件是还没有的,所以我们需要让其他组件也拥有这个store。

因此,install方法我们可以这样完善

let install = function(Vue){Vue.mixin({beforeCreate(){if (this.$options && this.$options.store){ // 如果是根组件this.$store = this.$options.store}else { //如果是子组件this.$store = this.$parent && this.$parent.$store}}})
}

解释下代码:

  1. 参数Vue,我们在第四小节分析Vue.use的时候,再执行install的时候,将Vue作为参数传进去。
  2. mixin的作用是将mixin的内容混合到Vue的初始参数options中。相信使用vue的同学应该使用过mixin了。
  3. 为什么是beforeCreate而不是created呢?因为如果是在created操作的话,$options已经初始化好了。
  4. 如果判断当前组件是根组件的话,就将我们传入的store挂在到根组件实例上,属性名为$store
  5. 如果判断当前组件是子组件的话,就将我们根组件的$store也复制给子组件。注意是引用的复制,因此每个组件都拥有了同一个$store挂载在它身上。

这里有个问题,为什么判断当前组件是子组件,就可以直接从父组件拿到$store呢?这让我想起了曾经一个面试官问我的问题:父组件和子组件的执行顺序

A:父beforeCreate-> 父created -> 父beforeMounte -> 子beforeCreate ->子create ->子beforeMount ->子 mounted -> 父mounted

可以得到,在执行子组件的beforeCreate的时候,父组件已经执行完beforeCreate了,那理所当然父组件已经有$store了。

六、实现Vuex的state

    <p>{{this.$store.state.num}}</p>

我们都知道,可以通过这个 语句获得 state的值
但是我们在Store类里还没实现,显然,现在就这样取得话肯定报错。

前面讲过,我们是这样使用Store的

export default new Vuex.Store({state: {num:0},mutations: {},actions: {},modules: {}
})

也就是说,我们把这个对象

{state: {num:0},mutations: {},actions: {},modules: {}
}

当作参数了。

那我们可以直接在Class Store里,获取这个对象

class Store{constructor(options){this.state = options.state || {}}
}

那这样是不是可以直接使用了呢?

试一下呗!

//App.vue
<template><div id="app">123<p>{{this.$store.state.num}}</p></div>
</template>

运行结果:

太赞了吧,怎么会这么简单。。。不敢相信。

哦不,当然没有这么简单,我们忽略了一点,state里的值也是响应式的哦,我们这样可没有实现响应式。

曾经面试官问我Vuex和全局变量比有什么区别。这一点就是注意区别吧

那要怎么实现响应式呢? 我们知道,我们new Vue()的时候,传入的data是响应式的,那我们是不是可以new 一个Vue,然后把state当作data传入呢? 没有错,就是这样。

class Store{constructor(options) {this.vm = new Vue({data:{state:options.state}})}}

现在是实现响应式了,但是我们怎么获得state呢?好像只能通过this.$store.vm.state了?但是跟我们平时用的时候不一样,所以,是需要转化下的。

我们可以给Store类添加一个state属性。这个属性自动触发get接口。

class Store{constructor(options) {this.vm = new Vue({data:{state:options.state}})}//新增代码get state(){return this.vm.state}}

这是ES6,的语法,有点类似于Object.defineProperty的get接口


成功实现。

七、实现getter

//myVuex.js
class Store{constructor(options) {this.vm = new Vue({data:{state:options.state}})// 新增代码let getters = options.getter || {}this.getters = {}Object.keys(getters).forEach(getterName=>{Object.defineProperty(this.getters,getterName,{get:()=>{return getters[getterName](this.state)}})})}get state(){return this.vm.state}
}

我们把用户传进来的getter保存到getters数组里。

最有意思的是经常会有面试题问:为什么用getter的时候不用写括号。要不是我学到这个手写Vuex,也不会想不明白,原来这个问题就像问我们平时写个变量,为什么不用括号一样。(如{{num}},而不是{{num()}}

原来就是利用了Object.defineProperty的get接口。

ok,现在来试一下,getter可不可以使用。

//store/index.js
import Vue from 'vue'
import Vuex from './myVuex'Vue.use(Vuex)export default new Vuex.Store({state: {num:0},// 新增测试代码getter:{getNum:(state)=>{return state.num}},mutations: {},actions: {},
})
<template><div id="app">123<p>state:{{this.$store.state.num}}</p><p>getter:{{this.$store.getters.getNum}}</p></div>
</template>

完美。毫无事故。

八、实现mutation

//myVuex.js
class Store{constructor(options) {this.vm = new Vue({data:{state:options.state}})let getters = options.getter || {}this.getters = {}Object.keys(getters).forEach(getterName=>{Object.defineProperty(this.getters,getterName,{get:()=>{return getters[getterName](this.state)}})})//新增代码let mutations = options.mutations || {}this.mutations = {}Object.keys(mutations).forEach(mutationName=>{this.mutations[mutationName] = (arg)=> {mutations[mutationName](this.state,arg)}})}get state(){return this.vm.state}
}

mutations跟getter一样,还是用mutations对象将用户传入的mutations存储起来。

但是怎么触发呢?回忆一下,我们是怎么触发mutations的。

this.$store.commit('incre',1)

对,是这种形式的。可以看出store对象有commit这个方法。而commit方法触发了mutations对象中的某个对应的方法,因此我们可以给Store类添加commit方法

//myVuex.js
class Store{constructor(options) {this.vm = new Vue({data:{state:options.state}})let getters = options.getter || {}this.getters = {}Object.keys(getters).forEach(getterName=>{Object.defineProperty(this.getters,getterName,{get:()=>{return getters[getterName](this.state)}})})let mutations = options.mutations || {}this.mutations = {}Object.keys(mutations).forEach(mutationName=>{this.mutations[mutationName] =  (arg)=> {mutations[mutationName](this.state,arg)}})}//新增代码commit(method,arg){this.mutations[method](arg)}get state(){return this.vm.state}
}

好了,现在来测试一下。

<template><div id="app">123<p>state:{{this.$store.state.num}}</p><p>getter:{{this.$store.getters.getNum}}</p><button @click="add">+1</button></div>
</template>
//新增测试代码
<script>export default {methods:{add(){this.$store.commit('incre',1)}}}
</script>

store/index.js

//store/index.js
import Vue from 'vue'
import Vuex from './myVuex'Vue.use(Vuex)export default new Vuex.Store({state: {num:0},getter:{getNum:(state)=>{return state.num}},// 新增测试代码mutations: {incre(state,arg){state.num += arg}},actions: {},
})

运行成功。

九、实现actions

当会实现mutations后,那actions的实现也很简单,很类似,不信看代码:

//myVuex.js
class Store{constructor(options) {this.vm = new Vue({data:{state:options.state}})let getters = options.getter || {}this.getters = {}Object.keys(getters).forEach(getterName=>{Object.defineProperty(this.getters,getterName,{get:()=>{return getters[getterName](this.state)}})})let mutations = options.mutations || {}this.mutations = {}Object.keys(mutations).forEach(mutationName=>{this.mutations[mutationName] =  (arg)=> {mutations[mutationName](this.state,arg)}})//新增代码let actions = options.actionsthis.actions = {}Object.keys(actions).forEach(actionName=>{this.actions[actionName] = (arg)=>{actions[actionName](this,arg)}})}// 新增代码dispatch(method,arg){this.actions[method](arg)}commit(method,arg){console.log(this);this.mutations[method](arg)}get state(){return this.vm.state}
}

一毛一样,不过有一点需要解释下,就是这里为什么是传this进去。这个this代表的就是store实例本身

这是因为我们使用actions是这样使用的:

  actions: {asyncIncre({commit},arg){setTimeout(()=>{commit('incre',arg)},1000)}},

其实{commit} 就是对this,即store实例的解构

那我们来测试一下。


<template><div id="app">123<p>state:{{this.$store.state.num}}</p><p>getter:{{this.$store.getters.getNum}}</p><button @click="add">+1</button><button @click="asyncAdd">异步+2</button></div>
</template><script>export default {methods:{add(){this.$store.commit('incre',1)},asyncAdd(){this.$store.dispatch('asyncIncre',2)}}}
</script>

store/index.js

//store/index.js
import Vue from 'vue'
import Vuex from './myVuex'Vue.use(Vuex)export default new Vuex.Store({state: {num:0},getter:{getNum:(state)=>{return state.num}},mutations: {incre(state,arg){state.num += arg}},//新增测试代码actions: {asyncIncre({commit},arg){setTimeout(()=>{commit('incre',arg)},1000)}},
})

oh my god,居然出错了,它这里错误 说的是执行到这里发现这里的this为undefined。

不过,不对啊,我们在实现mutation的时候也执行到这里了啊,而且执行成功了啊。

来分析一下:

this.$store.commit('incre',1)

执行这段代码的时候,执行commit的时候,this是谁调用就指向谁,所以this指向$store

this.$store.dispatch('asyncIncre',2)

执行这段代码,就会执行

asyncIncre({commit},arg){setTimeout(()=>{commit('incre',arg)},1000)
}

发现问题了吧?? 谁调用commit??是$store吗?并不是。所以要解决这个问题,我们必须换成箭头函数

//myVuex.js
class Store{constructor(options) {this.vm = new Vue({data:{state:options.state}})let getters = options.getter || {}this.getters = {}Object.keys(getters).forEach(getterName=>{Object.defineProperty(this.getters,getterName,{get:()=>{return getters[getterName](this.state)}})})let mutations = options.mutations || {}this.mutations = {}Object.keys(mutations).forEach(mutationName=>{this.mutations[mutationName] =  (arg)=> {mutations[mutationName](this.state,arg)}})let actions = options.actionsthis.actions = {}Object.keys(actions).forEach(actionName=>{this.actions[actionName] = (arg)=>{actions[actionName](this,arg)}})}dispatch(method,arg){this.actions[method](arg)}// 修改代码commit=(method,arg)=>{console.log(method);console.log(this.mutations);this.mutations[method](arg)}get state(){return this.vm.state}
}

再来测试

完美收官!!!!

补充:有群友问到一个问题,我觉得很有意思,就是说直接通过$store.state.xx = ""。可以吗?其实这样赋值也不会有问题,而且state依旧是响应式的。那么为什么用commit来多此一举呢?

vuex能够记录每一次state的变化记录,保存状态快照,实现时间漫游/回滚之类的操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RrkjCLrY-1596337606360)(https://imgkr2.cn-bj.ufileos.com/8617ccd9-c389-4512-bd9c-a55f8e972a68.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=yKwZcXm2glLeMFGVcaoF3dv6eXc%253D&Expires=1596423850)]

我有想到一件有意思的事情,要是说我们要实现一个最简单的Vuex,其实只实现state不就好了,其他的getter啊,action,commit都不实现。有种轻装上阵的感觉。其实也能实现。

而这样实现后发现其实跟全局变量差不多,只不过state是响应式的。

有什么不理解的或者是建议欢迎评论提出

感谢您也恭喜您看到这里,我可以卑微的求个star吗!!!

github:https://github.com/Sunny-lucking/howToBuildMyWebpack


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

相关文章

vuex实现原理

文章目录 vuex是什么&#xff1f;为什么会出现&#xff1f;怎么使用&#xff1f;怎么实现&#xff1f;1、给每个实例注入$store2、设置state响应数据3、getters4、mutations5、actions6、modules7、持久化插件plugins8、辅助函数mapState用法命名空间用法实现原理命名空间原理 …

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;…