提前说明:
- 本文不考虑使用 module 的情况,只考虑 vuex 的基本功能
- 本文不考虑多个 store 的情况
- 项目路径可能比较简单,如果需要扩展请读者自行调整一下
回顾一下 Vuex 如何使用?
- 声明一个
store
:
js
import { createStore } from 'vuex';
const store = createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
},
reset(state, payload) {
state.count = payload || 0;
}
},
actions: {
reset({ commit }, payload) {
console.log(commit, payload);
if (typeof payload === 'number') {
commit('reset', payload);
}
}
},
getters: {
doubleCount(state, getters) {
return state.count * 2;
}
},
});
export default store;
- 在 app 中使用插件:
js
import { createApp } from 'vue';
import App from './App.vue';
import './index.css';
import store from './store';
createApp(App).use(store).mount('#app');
- 在
App.vue
中使用:
html
<script setup>
import { computed } from 'vue';
import { useStore } from 'my-vuex';
const store = useStore();
const count = computed(() => store.state.count);
const doubleCount = computed(() => store.getters.doubleCount);
const addCount = () => {
store.commit('increment');
}
const resetCount = () => {
store.dispatch('reset', 3);
}
</script>
<template>
<div>
<h1>Count: {{ count }}</h1>
<h1>DoubleCount: {{ doubleCount }}</h1>
<button @click="addCount">Add Count</button>
<button @click="$store.commit('decrement')">Minus Count</button>
<button @click="resetCount">Reset Count</button>
</div>
</template>
思考:
假如我想要实现 vuex 的基本功能,命名为
my-vuex
该怎么办呢?
实现思路:
my-vuex
的整体目录结构初定:
perl
my-vuex
|- index.js # 项目的出口
|- creator.js # 创建工具的方法
|- hooks.js # 对外抛出的一些 hooks
- 程序从外往里设计 -
my-vuex
中导出两个方法:createStore
和useStore
js
/* 构建器 */
export { createStore } from './creator';
/* 仓库需要用到的 hooks */
export { useStore } from './hooks';
my-vuex
根目录下面创建一个配置文件config.js
,这里我们需要一个 唯一的 store 名称
js
/** store 的名称 */
export const STORE_NAME = Symbol('store');
-
实现一下
creator/createStore
方法,要求如下:- 这个方法接收
store
的配置选项,并需要封装代理这些store
配置功能(这里我们就是用面向对象的设计方式进行处理) - 这个方法返回一个 Vue 的插件, 返回的插件需要完成两件事:
- 向下注入 store
- 挂载全局对象 $store
- 这个方法接收
js
import { STORE_NAME } from './config';
import Store from './Store';
/**
* @function createStore
* @description 创建一个 store
* @param {object} config 配置 store 的选项
* @param {Record<string, any>} config.state store 需要的状态
* @param {{ [key: string]: ((state: Record<string, any>, payload: any) => void) }} config.mutations 变更 store 状态的方法集合
* @param {{ [key: string]: ((store: Store, payload: any) => void) }} config.actions 定义变更 store 值的 action
* @param {{ [key: string]: ((state: Record<string, any>, getters: any) => any) }} config.getters 定义计算获取的数据
* @return {{ install: (app: import('vue').App, options: any) => void }} 返回一个 store 插件
*/
export function createStore(config) {
try {
const { state, mutations, actions, getters } = config;
const store = new Store({
state,
mutations,
actions,
getters
});
return {
install(app, options) {
app.provide(STORE_NAME, store);
app.config.globalProperties.$store = store;
}
};
} catch (e) {
throw new Error(e);
}
}
- 实现一下
hooks/useStore
方法,这里直接使用inject(STORE_NAME)
即可:
js
import { inject } from 'vue';
import { STORE_NAME } from './config';
/**
* 在组件中使用 store
*/
export function useStore() {
return inject(STORE_NAME);
}
-
实现一下 Store 这个类,要求如下:
- 需要有一个公有的可读属性:state
- 需要有一个公有的可读属性:getters
- 需要有一个
dispatch(type, payload)
方法 - 需要有一个
commit(type, payload)
方法 - 内部需要有一个初始化代理的
_init()
方法
js
import { reactive } from 'vue';
class Store {
constructor({
state,
mutations,
actions,
getters,
}) {
// 把原来的配置存储在实例中
this._state = reactive(state);
this._mutations = mutations;
this._actions = actions;
this._getters = getters;
this._init();
}
_init() {
// ... 初始化操作
}
dispatch(type, payload) {}
commit(type, payload) {}
}
export default Store;
- 设置只读属性
state
js
import { reactive } from 'vue';
class Store {
actions = {};
mutations = {};
getters = {};
constructor({
state,
mutations,
actions,
getters,
}) {
// 把原来的配置存储在实例中
this._state = reactive(state);
this._mutations = mutations;
this._actions = actions;
this._getters = getters;
this._init();
}
// 在这里加入 state getter
get state() {
return this._state;
}
_init() {
// ... 初始化操作
}
dispatch(type, payload) {}
commit(type, payload) {}
}
export default Store;
-
实现一下 init 方法,要求如下:
- 代理 mutations 中的方法
- 代理 actions 中的方法
- 代理 getters 中的内容
所以,我们在这里需要在 _init()
方法中调用三个方法:
initMutations
initActions
initGetters
js
import { reactive } from 'vue';
import {
initActions,
initMutations,
initGetters
} from './init';
class Store {
actions = {};
mutations = {};
getters = {};
constructor({
state,
mutations,
actions,
getters,
}) {
// 把原来的配置存储在实例中
this._state = reactive(state);
this._mutations = mutations;
this._actions = actions;
this._getters = getters;
this._init();
}
get state() {
return this._state;
}
_init() {
// ... 初始化操作
initMutations(this._mutations, this);
initActions(this._actions, this);
initGetters(this._getters, this);
}
dispatch(type, payload) {}
commit(type, payload) {}
}
export default Store;
- 在
my-vuex
中创建一个init.js
文件,并在文件中分别实现一下initMutations
、initActions
、initGetters
这三个方法:
initMutations
js
/**
* @function initMutations
* @description 初始化 mutations 中的方法
* @param {{ [key: string]: ((state: Record<string, any>, payload: any) => void) }} mutations 变更 store 状态的方法集合
* @param {import('./Store').default} store
*/
export function initMutations(mutations, store) {
store.mutations = {};
for (let key in mutations) {
if (mutations.hasOwnProperty(key)) {
const fn = mutations[key].bind(store);
Object.defineProperty(store.mutations, key, {
get() {
return (payload) => {
fn(store._state, payload);
};
}
});
}
}
}
initActions
js
/**
* @function initActions
* @description 初始化 actions 中的方法
* @param {{ [key: string]: ((store: Store, payload: any) => void) }} actions 定义变更 store 值的 action
* @param {import('./Store').default} store
*/
export function initActions(actions, store) {
store.actions = {};
for (let key in actions) {
if (actions.hasOwnProperty(key)) {
const commit = store.commit.bind(store);
Object.defineProperty(store.actions, key, {
get() {
return (payload) => {
console.log(key, payload)
commit(key, payload);
}
}
});
}
}
}
initGetters
js
/**
* @function initGetters
* @description 初始化 actions 中的 getters
* @param {} getters
*/
export function initGetters(getters, store) {
store.getters = {};
for (let key in getters) {
if (getters.hasOwnProperty(key)) {
const fn = store._getters[key].bind(store);
Object.defineProperty(store.getters, key, {
get() {
return computed(() => {
return fn(store._state, store.getters);
});
}
});
}
}
}
到这里,我们的代码功能就已经实现完成了 😄😄😄