Appearance
从零实现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
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
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
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
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
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
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
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
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
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
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
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
2
3
4
5
util.js
export function forEachValue(obj, fn) { // 用于循环对象
Object.keys(obj).forEach(key => fn(obj[key], key))
}
1
2
3
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
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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
get state(){
return this._state.data
}
1
2
3
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
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
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
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
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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
replaceState原理
replaceState(state) {
this._withCommit(() => {
this._state.data = state
})
}
1
2
3
4
5
2
3
4
5
registerModule原理
store.registerModule(['aCount', 'cCount'], {// 注册一个新的模块
state: {
count: 0
}
})
1
2
3
4
5
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
2
3
4
5
6
7
8
9
10
11
执行installModule时会新增状态,我们在更改状态外添加
store._withCommit
保证严格模式下代码不会发生异常!
到此我们的代码全部搞定~~~ see you later