前端架构学习笔记(二)封装一个简单的 Vuex
如果有问题,或者有更好的解决方案,欢迎大家分享和指教。
一、最终目标
- 在任意组件中可以使用 $store 访问 store 中的数据
- state 都是响应式数据
- 实现通过 commit 调用 mutations
- 实现通过 dispatch 调用 actions
- 实现 getters,并借用 computed 计算属性缓存 getters 的值(getters) 也是响应式数据
二、目录
在 src
文件夹下新建以下文件
my-store
store.js // 封装 store
index.js // 配置 store
my-store/index.js
中的配置
import Vue from 'vue'
import Vuex from './store'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 0,
},
mutations: {
add(state) {
state.counter++;
}
},
actions: {
add({commit}) {
setTimeout(() => {
commit('add');
}, 1000);
}
},
getters: {
doubleCounter(state) {
return state.counter * 2;
},
troubleCounter(state) {
return state.counter * 3;
}
}
})
在 main.js
中做如下配置
import Vue from 'vue'
import App from './App.vue'
import store from './my-store/index'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
希望最终能完成 state 的调用,赋值,动态响应,以及 getters 的使用:
<!-- 任意 vue 组件中做如下调用 -->
<button @click="$store.commit('add')">同步改变 count</button>
<button @click="$store.dispatch('add')">异步改变 count</button>
<h3>{{ $store.state.counter }}</h3>
<h3>{{ $store.getters.doubleCounter }}</h3>
<h3>{{ $store.getters.troubleCounter }}</h3>
三、封装(my-store/store.js)
/*
* 1. 插件:挂载 $store
* 2. 实现一个数据管理类 store
* */
let Vue;
function install(_Vue) {
Vue = _Vue;
// 全局混入,将 $store 绑定给 Vue 原型,以便所有组件都能正常访问 $store
Vue.mixin({
beforeCreate() {
if(this.$options.store) {
Vue.prototype.$store = this.$options.store;
}
}
});
}
class Store {
constructor(options) {
const {state, mutations, actions, getters} = options;
/* 1. 将用户传入的 state 声明成响应式数据 */
/* *
* 借用 Vue 内部将组件实例的 data 转换成响应式数据的功能,将 state 转换成响应式数据(其实是借助 Observer 类)
* 可以 log 一下 vuex 的 state,走的的确也是 Observer 类
*
* 这里直接暴露 state,在实际的封装中是不可取的,可以为 state 设置一个 setter,和 getter
*
* this.state = new Vue({
* data: state || {}
* });
*/
/* 2. 处理 mutations */
this._mutations = mutations || {};
/* 3. 处理 actions */
this._actions = actions || {};
/* 5. 处理 getters */
this._wrapperGetters = getters || {};
this.getters = {};
// 借用 computed 实现值的缓存
const computed = this.registerGetters(state, this);
this._vm = new Vue({
data: {
$$state: state
},
computed
});
/* 4. 确定 this 指向,在确保 commit 和 dispatch 的时候 this 指向不会出问题 */
this.commit = this.commit.bind(this);
this.dispatch = this.dispatch.bind(this);
/* 5. 处理 getters */
}
get state() {
return this._vm._data.$$state;
}
set state(v) {
console.error('place user replaceState to reset state');
}
commit(type, payload) {
const entry = this._mutations[type];
if(!entry) {
console.error('unkown mutation type ' + type);
}
entry(this.state, payload);
}
dispatch(type, payload) {
const entry = this._actions[type];
if(!entry) {
console.error('unkown action type ' + type);
}
entry(this, payload);
}
/* 将 getters 注册成响应式数据 */
registerGetters(state, _this) {
const computed = {};
Object.keys(_this._wrapperGetters).forEach(key => {
const fn = _this._wrapperGetters[key];
// 将用户传入的 getters 的键,一次绑定给 _vm 的 computed,实现值的缓存功能
computed[key] = function() {
return fn(state);
};
Object.defineProperty(_this.getters, key, {
// getters 中都是只读值
get() {
// 取值时,根据 Vue实例的规则,会从 _vm 的 computed 中取到值,并且返回
return _this._vm[key];
}
});
});
return computed;
}
}
export default { Store, install }