手写Vuex
需求分析
Vue.use(Vuex)
=> 这是一个插件,需要实现一个静态方法 install
new Vuex.Store(options)
=> 需要实现一个Store类
- 页面中可以通过
this.$store.xxx
可以访问store实例 => 需要挂载$store
到Vue.prototype上
- 页面中可以通过
this.$store.state.xxx
可以动态的更新页面 => state是响应式对对象
- 页面中可以通过 `this.$store.getters.getCounter可以动态的更新页面 => getters是响应式对对象,getters.xxx是响应式的函数取值 => 考虑computed属性
- 实现2个方法
commit、dispatch
方法
代码实现
- main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
- store/index.js
import Vue from 'vue'
import Vuex from './KVuex'
Vue.use(Vuex)
new Vuex.Store({
state: {
counter: 1
},
mutations: {
syncAdd(state, multiple){
state.counter += multiple
}
},
actions: {
asyncAdd({ commit }, multiple){
setTimeout(() => {
commit('syncAdd',multiple)
},300)
}
},
getters: {
getCounter: state => state.counter,
getCounterByDivisor(state){
return divisor => state.counter/ divisor
},
getCounter3: state => divisor => state.counter / divisor
}
})
- 页面中展示
<p>{{ $store.state.counter }}</p>
<p @click="$store.commit('syncAdd(2)')"> 同步提交数据 </p>
<p @click="$store.dispatch('asyncAdd(4)')"> 异步提交数据 </p>
<p>{{ $store.getters.getCounterByDivisor(4) }}</p>
- 代码展示
// kVuex需求分析
// 1. new Vuex.Store() => 实现一个Store类,而且还不能直接导出Store类,反而要导出一个对象,对象里有Store
// 2. Vue.use(Vuex) => 插件,要实现一个install方法, install 方法不是Store的静态方法,而是对象Vuex的 静态方法
// 3. vue文件中通过 this.$store 访问store实例 => 挂载$store到原型上
// 4. 在main.js中,先①引入store实例的代码,再②new Vue()传入store参数
// => ①import store时,先进行Vue.use(Vuex),即执行install方法,在该方法中进行$store挂载: this.$options.store && (Vue.prototype.$store = this.$options.store), 如果this.$options.store存在,就进行挂载。this.$options.store 这里是new Vue({store})传入的options,这个传参是在②中执行的,现在还在执行①的代码,就想把②的代码拿来用
// 解决方案:
// ① 挂载$store放在setTimeout中,延迟执行 => 代码low、延迟时间不可控
// ② 使用Vue.mixin,在beforeCreate钩子函数中进行执行挂载,在beforeCreate执行时,new Vue({store})是一定执行了的,不然进不了这个钩子函数
// 5. 页面中 this.$store.state.counter 是响应式的 => state是响应式数据
// 6. commit、dispatch、getter方法
// 7. 页面中 this.$store.getters.getCounter 是响应式的 => getters是响应式数据, 而且,getters里面存储的是方法,考虑使用computed计算属性来实现
let Vue;
class Store {
constructor(options) {
const store = this;
this.$options = options;
this._mutations = options.mutations;
this._actions = options.actions;
this._wrappedgetters = options.getters;
this.getters = {};
// computed : {foo(){}}没有参数的函数。而getter是有参数的
const computed = {};
Object.keys(this._wrappedgetters).forEach((key) => {
computed[key] = function () {
return store._wrappedgetters[key](store.state);
};
// 设置getters[key],只读属性。 并且利用computed计算属性来获取getters[key]的值,且为响应式
Object.defineProperty(store.getters, key, {
get() {
return store._vm[key];
},
});
});
// 响应式数据 state
// ① Vue.util.defineReactive => 是用来定义对象的属性为响应式
// ② 借鸡生蛋 vue实例的data对象是响应式的
// this.state = options.state
// this.state = new Vue({ data: options.state }); // this.state.counter => vue实例.counter
// 将state保护起来
// 响应式getters是响应式、方法 => computed计算属性来实现
// this.$store.getters.getCounter / this.$store.getters.getCounterById
this._vm = new Vue({
data: {
$$state: options.state, // 在data中定义 $$xxx, 在实例中是获取不到该值的,被保护起来了。可以通过 this.$data.$$state获取
},
computed,
});
// 给commit的this指向绑定到store实例上
// this.commit = this.commit.bind(store); 这个跟下面的函数是一个意思,只是call执行起来性能比bind好一点
// bind、call、apply都是用来改变函数执行时的上下文的。
// 性能(时间消耗从少到多): call > bind > apply 但是,如果我们需要传入数组,即使有es6的解构的方法,apply的性能还是要优于call/bind
// ① bind : Function.bind(obj, arg1, arg2, arg3, ……) => 返回值是函数,需要手动调用
// ① call : Function.call(obj, arg1, arg2, arg3, ……) => apply、call是立即调用
// ① apply : Function.apply(obj, [argArray]) => call新能要优于apply,可能是因为apply解析数组的时候要耗费性能?
const { commit, dispatch } = store;
this.commit = function boundCommit(type, payload) {
commit.call(store, type, payload);
};
this.dispatch = function boundDispatch(type, payload) {
return dispatch.call(store, type, payload);
};
}
get state() {
return this._vm._data.$$state;
}
commit(type, payload) {
const entry = this._mutations[type];
if (entry) {
entry(this.state, payload);
}
}
// dispatch('setCounter', payload)
// addAsync({ commit }, payload) {
// setTimeout(() => {
// commit("addSync", payload); // 实际应用的时候是不
// });
// }
// 注意this指向问题
dispatch(type, payload) {
const entry = this._actions[type];
if (entry) {
// ① 考虑到异步操作,有可能会返回promise,进行操作,所以要return 一下
// ② 在entry内部执行的时候,如果有setTimeout等,就会涉及到this指向问题,所以需要把 commit函数的this绑定到该store实例上。
return entry(this, payload); // 异步
}
}
}
const install = function (_Vue) {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
// this执行vue实例
if (this.$options.store) {
// 说明是根Vue,实例化中传入的参数
Vue.prototype.$store = this.$options.store;
}
},
});
};
const Vuex = {
Store,
};
Vuex.install = install;
export default Vuex;