前言
不知不觉发现现在的文章很多围绕着面试展开,我也来蹭蹭这种氛围。
我相信对于技术栈是vue
的攻城狮
来讲,光光只会应用vue
是远远不够的,文本主要以状态管理器vuex
来展开,让我们来手写一个属于我们自己的vuex
吧!
一、vuex是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension (opens new window),提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
1.🤪了解vuex
在我们的项目里面,如果在多个页面或者多个组件之间存在着某种依赖关系,这些页面都需要共享一个状态的时候,这时候就出现了以下问题:
- 多个页面共享一个状态。
- 一个页面修改了状态,另外的页面也需要跟着改动。
很多小伙伴可能会说利用可以利用父子组件传参来解决。但是当我们的项目过于庞大了之后,不利于维护这种状态,以至于到最后 ~~~~~ 你懂得
。
2.🤯安装使用vuex
进入项目,在命令行中输入安装指令,回车
npm install vuex --save
然后再src/store/
目录下创建一个index.js
文件
import Vue from "vue";
import Vuex from "../vuex/index";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
name: 'zxl'
},
mutations: {
setName(state) {
state.name += '1';
},
},
actions: {
setName(store){
store.commit("setName");
}
},
getters: {
},
})
在入口文件main.js
引入
import Vue from 'vue';
import App from './App.vue';
import store from "../src/store/store";//在此引入
Vue.config.productionTip = false;
new Vue({
store,++++++++++++++++++++
render: h => h(App),
}).$mount('#app');
在页面中引用状态
<template>
<div class="hello">
{{$store.state.name}}
</div>
</template>
具体的应用小编这里就不在做过多的介绍了。
二、手动实现一个vuex
1.😇先观察我们配置的文件
为了能够更清晰手写vuex
,我们先来看下我们上面配置的index.js
文件。
- 首先导入了
vuex
和vue
。 - 直接在
vue
上注册了vuex
,从这里我们可以知道vuex
可以被vue。use
方法使用,vuex
里面必定提供了一个install
方法。 - 直接执行了
new Vuex.store
,从这里我们又可以知道vuex
提供了Store
属性,该属性是一个class
,可以被new
关键字使用,构造函数里面传入我们的配置对象。
接下来我们就跟着这个步骤来实现属于我们自己得状态管理器吧!
2.👶install注册函数的实现
我们在我们自己项目src
目录下创建一个vuex
文件,这个文件就放我们自己写的代码,然后再目录下创建一个index.js
文件。
在index.js
我们需要导出个对象,对象里面有两个东西,一个是install
方法、一个是Store
类。
export default {
Store,
install
}
首先我们现在实现下install
方法。
let Vue;
function install(_Vue) {
Vue = _Vue;//缓存Vue构造函数,后面要用到
Vue.mixin({
beforeCreate() {
if (this.$options.store) {//如果是根实例
this.$store = this.$options.store;
} else {//如果不是根实例,就拿父亲实例上的$store
this.$store = this.$parent && this.$parent.$store;
}
}
})
}
利用Vue.mixin
来进行全局混入,在所有组件上 beforeCreate 生命周期注入了设置 this.$store 这样一个对象。
2.😨state实现
前面我们实现了install
方法,那么接下来就实现这个核心Store
类吧!
class Store{
constructor(options){//options是我们传入的配置对象
}
}
state
和我们全局对象的区别就是当我们修改状态时,会触发响应式来更新试图。所以在这里尤大大
果然还是个聪明人。我们知道在组件里,data
上定义的属性是已经经过响应式化的,那么可以不可以把state
也定义在vue
的data
上。
class Store{
constructor(options){//options是我们传入的配置对象
this.vm = new Vue({//在这里又new了一个vue
data: {
state: options.state,//在data上定义了state对象
}
})
}
get state() {//获取state
return this.vm.state;
}
}
3.🤩getters实现
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
class Store{
constructor(options){//options是我们传入的配置对象
let getters = options.getters;//拿到配置对象的getters对象
this.getters = {};//在当前实例上定义getters
Object.keys(getters).forEach(getterName => {
Object.defineProperty(this.getters, {
get() {
return getters[getterName](this.state);//执行getters中的回调函数,将自身state传入
}
})
})
}
}
- 先拿到配置对象上的
getters
对象。 - 在
Store
实例上定义了getters
。 - 遍历对象
key
,通过Object.defineProperty
在自身this.getters
上定义了get
监听,将自身state
作为参数执行了配置对象中getters
回调函数。
4.🤔mutations实现。
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler) 。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
class Store{
constructor(options){//options是我们传入的配置对象
let mutations = options.mutations;
this.mutations = {};
Object.keys(mutations).forEach(mutationName => {
this.mutations[mutationName] = (payload) => {
mutations[mutationName](this.state, payload);
}
})
}
commit(mutationName, payload) {
this.mutations[mutationName](payload);
}
}
代码逻辑大致是相同的,只不过多了一个commit
方法。
5.🤖actions实现。
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用
context.commit
提交一个 mutation,或者通过context.state
和context.getters
来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。
class Store{
constructor(options){//options是我们传入的配置对象
let actions = options.actions;
this.actions = {};
Object.keys(actions).forEach((actionName) => {
this.actions[actionName] = (payload) => {
actions[actionName](this, payload);
}
})
}
dispatch(actionName, payload) {
this.actions[actionName](payload);
}
}
自此大家可以看看自己得一个基本的状态管理器能够正常使用了
把vuex
的引入改成自己文件的路径。
6.🤖modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
这里我需要维护这样一个modules
的父子关系。
class Store{
constructor(options){//options是我们传入的配置对象
this.modules = new ModuleCollection(options);
installModule(this, this.state, [], this.modules.root);
}
}
class ModuleCollection {
constructor(options) {
this.register([], options);
}
register(path, rootModule) {
let rawModule = {
_raw: rootModule,
_children: {},//儿子modules
state: rootModule.state,//当前的state
}
if (!this.root) {//如果是根module
this.root = rawModule;
} else {
let parentModule = path.slice(0, -1).reduce((root, current) => {
return root._children[current];
}, this.root);
parentModule._children[path[path.length - 1]] = rawModule;
}
if (rootModule.modules) {//如果有孩子,options上定义了modules
Object.keys(rootModule.modules).forEach(moduleName => {
this.register(path.concat(moduleName), rootModule.modules[moduleName]);
})
}
}
}
不知道为什么掘金
写文章今天上传图片一直失败,本来还想把生成的modules给大家看下的。大家可以打印下自己项目里的this.$store
,里面会有个_modules,这段代码的作用就是一个module
里可以新添加一个module
,维护了子父module
这种关系。
修改之前的代码
function installModule(store, rootState, path, rawModule) {
if (path.length > 0) {//如果是子模块
let parentState = path.slice(0, -1).reduce((root, current) => {
return rootState[current];
}, rootState)
Vue.set(parentState, path[path.length - 1], rawModule.state);//将子module的状态state定义到根模块上。
}
let getters = rawModule._raw.getters;
if (getters) {//定义getters
if (!store.getters) {
store.getters = {};
}
Object.keys(getters).forEach(getterName => {
Object.defineProperty(store.getters, getterName, {
get() {
return getters[getterName](rawModule.state);
}
})
})
}
let mutations = rawModule._raw.mutations;
if (mutations) {
Object.keys(mutations).forEach(mutationName => {
if (!store.mutations) {
store.mutations = {};
}
let arr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
arr.push((payload) => {
mutations[mutationName](rawModule.state, payload);
})
})
}
let actions = rawModule._raw.actions;
if (actions) {
Object.keys(actions).forEach(actionName => {
if (!store.actions) {
store.actions = {};
}
let arr = store.actions[actionName] || (store.actions[actionName] = []);
arr.push((payload) => {
actions[actionName](store, payload);
})
})
}
let keys = Object.keys(rawModule._children);
keys.forEach(moduleName => {
installModule(store, rootState, path.concat(moduleName), rawModule);
})
}
这段代码和我们上面的操作是基本一样的,不同的区别就是它通过vue.set
将子module
的状态state
定义到根模块上。
7.🤗registerModule
registerModule(moduleName, module) {//动态加载模块
if (!Array.isArray(moduleName)) {
moduleName = [moduleName];
}
this.modules.register(moduleName, module);
installModule(this, this.state, moduleName, module);
}