Skip to content
On this page

从零实现Vuex4

一.在Vue3中使用vuex

store.js

import { createStore } from 'vuex'
export default createStore({
    state: { // 状态
        count: 0
    },
    getters: { // 计算属性
        double(state) {
            return state.count * 2
        }
    },
    mutations: { // 同步方法
        add(state, payload) {
            state.count += payload;
        }
    },
    actions: { // 异步方法
        asyncAdd({ commit }, payload) {
            setTimeout(() => {
                commit('add', payload)
            }, 1000);
        }
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

main.js

import store from './store'
createApp(App).use(store /*injectKey*/).mount('#app')
1
2

App.vue

<template>
  <div>当前数量:{{count}} {{$store.state.count}}</div>
  <div>翻倍 :{{double}} {{$store.getters.double}}</div>
  <button @click="add">+</button>
  <button @click="asyncAdd">异步+</button>
</template>
<script>
import {computed} from 'vue'
import {useStore} from 'vuex'
export default {
  name: 'App',
  setup(){
    const store = useStore();
    return {
      count:computed(()=>store.state.count),
      double:computed(()=>store.getters.double),
      add:()=>store.commit('add',1),
      asyncAdd:()=>store.dispatch('asyncAdd',2)
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

二.Vuex原理编写

Vuex的原理:创建一个公共store,在所有的组件中都可以获取这个store属性中的状态。

import { inject } from 'vue';
const storeKey = 'store';
class Store {
    constructor(options) {

    }
    install(app, injectKey) {
        app.provide(injectKey || storeKey, this); // 将当前store暴露出来
        app.config.globalProperties.$store = this; // 添加全局属性$store
    }
}
function createStore(options) {
    return new Store(options);
}
function useStore(key = null) {
    return inject(key != null ? key : storeKey); // 在任意组件中均可注入属性
}
export {
    createStore,
    useStore
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

1.实现state

class Store {
    constructor(options) {
        const store = this;
        const state = options.state;
        store._state = reactive({ data: state }); // 响应式数据
    }
    get state() {
        return this._state.data;
    }
    install(app, injectKey) {
        app.provide(injectKey || storeKey, this);
        app.config.globalProperties.$store = this;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

2.实现getters

export function forEachValue(obj, fn) { // 用于循环对象
    Object.keys(obj).forEach(key => fn(obj[key], key))
}
1
2
3
store.getters = Object.create(null);
// 处理getters, 获取用户定义的getters,增添到store上
const _getters = options.getters;
forEachValue(_getters, (fn, key) => {
    Object.defineProperty(store.getters, key, {
        get: () => fn(store.state),
        enumerable: true
    })
});
1
2
3
4
5
6
7
8
9

3.实现mutation和action

store._mutations = Object.create(null);
const _mutations = options.mutations;
// 获取用户的mutations 增添到store上
forEachValue(_mutations,(mutation, key) => {
    store._mutations[key] = (payload) => {
        mutation.call(store, store.state, payload)
    }
})
store._actions = Object.create(null);
const _actions = options.actions;
// 获取用户的actions 增添到store上
forEachValue(_actions,(action, key) => {
    store._actions[key] = (payload) => {
        action.call(store, store, payload);
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

4.实现dispatch 及 commit方法

commit =(type,payload)=>{ // 触发对应的mutation
    const handler = this._mutations[type];
    handler(payload);
}
dispatch = (type,payload)=>{ // 触发对应的action
    const handler = this._actions[type];
    handler(payload);
}
1
2
3
4
5
6
7
8

三.完整Vuex实现

import { createStore } from 'vuex'
export default createStore({
    // ...
    modules: {
        aCount: {
            state: { count: 0 },
            mutations: {
                add(state, payload) {
                    state.count += payload;
                }
            }
        },
        bCount: {
          state: { count: 0 },
          mutations: {
              add(state, payload) {
                  state.count += payload;
              }
          }
      }
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

我们期望的是将状态进行合并, 所有的mutation进行合并。 同理getters、actions也是一样

store.state = {
  count:0,
  aCount:{count:0},
  bCount:{count:0}
}
state._mutations['add'] = [rootAddMutation,aAddMutation,bAddMutation];
1
2
3
4
5
6

1.实现拆分

injectKey.js

import { inject } from 'vue';
export const storeKey = 'store';
export function useStore(key = null) {
    return inject(key != null ? key : storeKey)
}
1
2
3
4
5

util.js

export function forEachValue(obj, fn) { // 用于循环对象
    Object.keys(obj).forEach(key => fn(obj[key], key))
}
1
2
3

store.js

import {storeKey} from './injectKey'
export class Store {
    constructor(options) {}
    install(app, injectKey) {
        app.provide(injectKey || storeKey, this);
        app.config.globalProperties.$store = this;
    }
}
1
2
3
4
5
6
7
8

index.js

import { Store } from './store';
import { useStore } from './injectKey';
function createStore(options) {
    return new Store(options);
}
export {
    createStore,
    useStore
}
1
2
3
4
5
6
7
8
9

2.用户数据格式化

class Module {
    constructor(rawModule) {
        this._rawModule = rawModule;
        this._children = Object.create(null);
        this.state = rawModule.state
    }
    getChild(key) {
        return this._children[key];
    }
    addChild(key, module) {
        this._children[key] = module;
    }
    forEachChild(callback){
        forEachValue(this._children,callback)
    }
}
class ModuleCollection { // 递归格式化数据
    constructor(rawRootModule) {
        this.register([], rawRootModule)
    }
    register(path, rawModule) {
        const newModule = new Module(rawModule);
        if (path.length == 0) {
            this.root = newModule;
        } else {
            const parent = path.slice(0, -1).reduce((module, key) => {
                return module.getChild(key)
            },this.root);
            parent.addChild(path[path.length - 1], newModule);
        }
        if(rawModule.modules){
            forEachValue(rawModule.modules,(rawChildModule,key)=>{
                this.register(path.concat(key),rawChildModule)
            })
        }
    }
}
this._modules = new ModuleCollection(options);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

最终格式化的结果是

{
    state:{count:0},
    _children:{
        aCount:{_rawModule:{},_children:{},state:{count:0}},
        bCount:{_rawModule:{},_children:{},state:{count:0}},
    },
    _rawModule: {}
}
1
2
3
4
5
6
7
8

3.状态的安装

installModule(this,state,[],this._modules.root);
function installModule(store,rootState,path,module){
    const isRoot = !path.length;
    if(!isRoot){
        const parentState = path.slice(0,-1).reduce((state,key)=>state[key],rootState);
        const moduleName = path[path.length - 1];
        parentState[moduleName] = module.state;
    }
    module.forEachChild((child,key)=>{
        installModule(store,rootState,path.concat(key),child)
    })
}
1
2
3
4
5
6
7
8
9
10
11
12

将子模块的状态定义到根状态上。

4.其他属性的安装

module.forEachMutation((mutation, key) => {
    const entry = store._mutations[key] || (store._mutations[key] = []);
    entry.push((payload) => {
        mutation.call(store,getNestedState(store.state,path), payload)
    })
})
module.forEachAction((action, key) => {
    const entry = store._actions[key] || (store._actions[key] = []);
    entry.push((payload) => {
        let res = action.call(store, store, payload);
        if (!isPromise(res)) {
            return Promise.resolve(res);
        }
        return res;
    })
})
module.forEachGetter((getter, key) => {
    store._wrappedGetters[key] = function wrapperdGetter() {
        return getter(getNestedState(store.state,path));
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

将所有模块的属性进行收集

function getNestedState (state, path) {
    return path.reduce((state, key) => state[key], state)
 }
1
2
3

这里要注意必须从store上获取状态,因为store上的状态才是响应式的,module.state 是普通对象,数据变化后,没有更新视图的能力

5.响应式状态及getters

function resetStoreState(store,state){
    store._state = reactive({ // 将状态设置成响应式
        data:state
    });
    const wrappedGetters = store._wrappedGetters;
    store.getters = {};
    forEachValue(wrappedGetters,(fn,key)=>{ // 设置计算属性
        Object.defineProperty(store.getters,key,{
            get:()=> fn(),
            enumerable:true
        })
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
get state(){
	return this._state.data
}
1
2
3

获取store中的状态,将状态标记为响应式,将getters定义在store上

6.实现dispatch及commit方法

commit = (type,payload) => {
    const entry = this._mutations[type];
    entry.forEach(handler=>handler(payload));
}
dispatch = (type,payload) => {
    const entry = this._actions[type];
    return Promise.all(entry.map(handler=>handler(payload)));
}
1
2
3
4
5
6
7
8

四.命名空间实现

modules: {
    aCount: {
        namespaced:true, // 添加命名空间
        state: { count: 0 },
        mutations: {
            add(state, payload) {
                state.count += payload;
            }
        }
    },
    bCount: {
        namespaced:true, // 添加命名空间
        state: { count: 0 },
        mutations: {
            add(state, payload) {
                state.count += payload;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export class Module {
    constructor(rawModule) {
        this._rawModule = rawModule;
        this._children = Object.create(null);
        this.state = rawModule.state;
        this.namespaced = rawModule.namespaced // 标记模块带有命名空间
    }
}    
1
2
3
4
5
6
7
8
export class ModuleCollection {
    getNamespace(path) {
        let module = this.root
        return path.reduce((namespace, key) => {
            module = module.getChild(key)
            return namespace + (module.namespaced ? key + '/' : '')
        }, '')
    }
}
1
2
3
4
5
6
7
8
9

添加获取命名空间的方法

function installModule(store, rootState, path, module) {
    const isRoot = !path.length;
    const namespace = store._modules.getNamespace(path); // 获取命名空间
    module.forEachMutation((mutation, key) => {
        const entry = store._mutations[namespace + key] || (store._mutations[namespace + key] = []); // 修改类型
        entry.push((payload) => {
            mutation.call(store, getNestedState(store.state, path), payload)
        })
    })
    module.forEachAction((action, key) => {
        const entry = store._actions[namespace + key] || (store._actions[namespace + key] = []); // 修改类型
        entry.push((payload) => {
            let res = action.call(store, store, payload);
            if (!isPromise(res)) {
                return Promise.resolve(res);
            }
            return res;
        })
    })
    module.forEachGetter((getter, key) => { // 修改类型
        store._wrappedGetters[namespace + key] = function wrapperdGetter() {
            return getter(getNestedState(store.state, path));
        }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

五.mutation和action的区别

export default createStore({
    strict:true,
    // ...
})
1
2
3
4

开启严格模式后,只能在mutation中才能进行状态的更改

export class Store {
    constructor(options) {
        this.strict = options.strict || false; // 是不是严格模式
        this._committing = false; // 默认不是在mutation更改数据
    }
    _withCommit(fn) {
        const committing = this._committing
        this._committing = true
        fn()
        this._committing = committing
    }
    commit = (type, payload) => {
        const entry = this._mutations[type];
        this._withCommit(()=>{ // commit方法中将committing置为true
            entry.forEach(handler => handler(payload));
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function resetStoreState(store, state) {
    // ...
    if (store.strict) { 
        enableStrictMode(store) // 启用严格模式
    }
}
function enableStrictMode(store) {
    watch(() => store._state.data, () => { // 创建同步watcher观测状态变化
        console.assert(store._committing, 'do not mutate vuex store state outside mutation handlers');
    }, { deep: true, flush: 'sync' }); // 标记flush:sync ,状态变化后会立即触发回调方法
}
1
2
3
4
5
6
7
8
9
10
11

六.插件机制实现

const customPlguin = function(store) {
    store.subscribe(function(mutation,state){
        console.log(mutation,state); // 每次调用commit时都会执行此方法
    })
}
export default createStore({
    strict: true,
    plugins: [ // 配置插件
        customPlguin
    ]
})
1
2
3
4
5
6
7
8
9
10
11
options.plugins.forEach(plugin => plugin(this)); // 内部会按照顺序依次执行插件
1

subsribe实现

this._subscribers = []; // 用于定义函数
export class Store {
    commit = (type, payload) => {
        const entry = this._mutations[type];
        this._withCommit(()=>{
            entry.forEach(handler => handler(payload));
        });
        this._subscribers.forEach(sub => sub({type,payload}, this.state));// 每次提交触发都触发一遍订阅的函数
    }
    dispatch = (type, payload) => {
        const entry = this._actions[type];
        return Promise.all(entry.map(handler => handler(payload)));
    }
    subcribe(fn){
        this._subscribers.push(fn); // 订阅事件
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

七.replaceState、registerModule实现

persists插件实现

const customPlguin = function(store) {
    let local = localStorage.getItem('VUEX:STATE');
    if(local){
        store.replaceState(JSON.parse(local))
    }
    store.subscribe(function(mutation, state) {
         localStorage.setItem('VUEX:STATE',JSON.stringify(state));
    })
}
1
2
3
4
5
6
7
8
9

replaceState原理

replaceState(state) {
    this._withCommit(() => {
        this._state.data = state
    })
}
1
2
3
4
5

registerModule原理

store.registerModule(['aCount', 'cCount'], {// 注册一个新的模块
    state: {
        count: 0
    }
})
1
2
3
4
5
class Store{
	registerModule(path, rawModule) {
        if (typeof path == 'string') path = [path];
        this._modules.register(path, rawModule); // 注册模块
        let module = path.reduce((module, key) => { // 获取注册后的模块
            return module.getChild(key)
        },  this._modules.root);
        installModule(this, this.state, path, module); // 安装模块
        resetStoreState(this,this.state); // 重置状态
    }
}
1
2
3
4
5
6
7
8
9
10
11

执行installModule时会新增状态,我们在更改状态外添加 store._withCommit 保证严格模式下代码不会发生异常!

到此我们的代码全部搞定~~~ see you later

Released under the MIT License.