前言
这篇文章实现了vueX的基础功能,小白一看就会,不考虑modules模块嵌套、命名空间的场景。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
分析
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: { // 存放状态 相当于new Vue({data}) 并且这里存放的数据时响应式的
age: 18,
},
mutations: { // 从规范上来说,这是唯一能够修改state中数据的地方(主要是为了方便跟踪state的改变),并且只能书写同步代码
changeAge(state, payload) {
state.age += payload;
}
},
actions: { // 通过mutations间接修改state中的数据,可以书写异步代码
asyncChangeAge(store, payload) {
setTimeout(() => {
store.commit("changeAge", payload);
}, 1000);
}
},
getters: { // VueX的计算属性 相当于new Vue({computes}) 具有懒惰性,与compute不同的是,getters可以作用于多个组件
otherAge(state) {
return state.age + 10;
}
}
})
export default store;
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
以上就是vueX的一个简单实用方式,接下来我们的对它的使用方式分析一下。首先我们会使用import引入vuex,接着使用vue.use(vuex),说明我们的vuex应该是一个插件形式的,且拥有一个install()方法。然后实例化了一个new Vuex.Store({})对象,并且提供的参数对象里有state、mutations、actions、getters属性,那么我们的Store应该是一个类或者构造函数。接着在vue的根实例上引入了刚才Store的实例化对象,这里其实是让根实例以及子组件都可以访问到Store的实例化对象,简称该对象为$store吧。
实现
主文件
import { Store, install } from "./store";
export default{
Store,
install,
}
export {
Store,
install,
}
主文件主要是集合其他文件。
install()
install方法里面主要是在beforeCreate钩子实现让所有子组件都可以访问到store属性,从而实现每个组件都可以访问到$store。
const install = (_Vue) => {
Vue = _Vue;
applyMixin(Vue);
}
const applyMixin = (Vue) => {
Vue.mixin({
beforeCreate: vuexInit,
})
}
function vuexInit(){
const options = this.$options;
// 让每个组件都可以访问$store对象
if(options.store){
// 根
this.$store = options.store;
}else if(options.parent && options.parent.$store){
// 子组件
this.$store = options.parent.$store;
}
}
state
class Store{
constructor{
...
let state = options.state;
this._vm = new Vue({
data: {
$$state: state,
},
})
}
...
get state(){
return this._vm._data.$$state;
}
}
state里面存储的数据是响应式的,当数据发生改变时视图也会更新,这里主要是利用了vue的响应式系统。vuex在内部直接创建了一个vue实例,将state状态数据放在vue的data中,然后对Store类添加类属性访问器,在属性访问器里面代理访问存储data的state状态数据,从而实现依赖收集,当依赖发生改变就会去通知渲染watcher更新页面。
为什么在data里面声明的state前面带有$$符号呢?因为当属性名是类似'$xxx',那么它不会被代理到vue的实例上。
mutations
按照vuex的规范来说,修改state中数据的唯一方法是通过commit触发mutations里面定义的方法,这主要是为了方便vuex跟踪数据的变化,并且mutations里面只能写同步方法。当然你也可以简单粗暴的使用this.$store.state.xxx = "",它同样也不会报错,也会得到视图响应式更新,但是这是一种不规范的写法。
export const forEach = (obj = {}, fn) => {
Object.keys(obj).forEach((key, index) => fn(obj[key], key));
}
class Store{
constructor{
...
this._mutations = {};
orEach(options.mutations, (fn, type) => {
this._mutations[type] = (payload) => fn.call(this, this.state, payload)
})
}
...
commit = (type, payload) => {
this._mutations[type](payload);
}
}
采用发布订阅模式,将用户定义的mutation先保存起来,稍后调用commit时 就找订阅的mutation方法,执行订阅方法。
actions
actions是通过mutations间接修改state里面的数据,并且在actions里面可以书写异步方法。它的实现过程和mutations差不多。
class Store{
constructor{
...
this._actions = {};
forEach(options.actions, (fn, type) => {
this._actions[type] = (payload) => fn.call(this, this, payload)
})
}
...
dispatch = (type, payload) => {
this._actions[type](payload);
}
}
同样是发布订阅模式,将用户定义的actions先保存起来,稍后调用dispatch时 就找订阅的actions方法,执行订阅方法。
getters
getters可以理解为vuex的计算属性,类似于vue的computed属性,getters同样是惰性求值,只有当它的依赖值发生改变才会重新计算新的值。getters和computed的区别在于,前者可以作用于多个组件,而后者只是作用于当前组件。getters的本质还是借用computed来实现的。
class Store{
constructor{
...
this.getters = {};
const computed = {};
forEach(options.getters, (fn, key) =>{
computed[key] = () => { // 借用计算属性来实现懒加载
return fn(this.state);
}
Object.defineProperty(this.getters, key, {
get: () => this._vm[key],
})
})
this._vm = new Vue({
data: {
$$state: state,
},
computed,
})
}
...
}
结尾
至此,就已经实现vuex的基本功能了,有兴趣的朋友可以继续查看完整项目代码,代码地址:vue-vuex-mini。
仓库代码已经实现了modules模块化、namespaced命名空间、plugins、动态注册模块,并且具有超详细的注释。