首先,我们得弄弄清楚什么是vuex?vuex的使用方法和原理是什么?vuex做了哪些事?最后我们才能参考手写一个vuex。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
多个视图依赖于同一状态。
来自不同视图的行为需要变更同一状态。
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。
这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux 和 The Elm Architecture。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
value由我们在仓库store的state中定义。
fn由我们在仓库store的Mutations中定义,用于提交更新state中定义的value,在vuex中我们只能通过这种方式去更新value。
fn由我们在仓库store的Actions中定义。payload为fn的参数,可为空。可为异步调用。
fn由我们在仓库store的getters定义,用于我们对state中的value进行派生操作。
function install(_Vue) {
Vue = _Vue
// 1.挂载$router。
// 由于install会由use调用,调用时,VueRoter对象可能会创建,所以使用全局混入
// VueRouter由根实例(app.vue中的vue对象)传入
// 全局混入: 混入能够增加组件方法的复用性
Vue.mixin({
beforeCreate() {
// 此钩子函数在每个组件创建之前都会执行
// this.$options.router只有根组件存在
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
}
由以上可知,vuex拥有state,mutations,actions,getters等。当然还有modules,但是我们手写的vuex中暂不实现,后面有时间再讨论。
import Vue from 'vue'
import Vuex from './jvuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 0
},
mutations: {
add(state) {
// state从哪来?
state.counter++
}
},
actions: {
add({commit}) {
// 参数是什么,哪来的?
setTimeout(() => {
commit('add')
}, 1000)
}
},
getters: {
doubleCounter(state) {
return state.counter * 2
}
}
})
// vue插件可以由function构成,必须有install方法,该方法会被vue.use调用
// install方法会会接收一个vue对象参数
// 为了不打包vue,先声明,由install参数赋值
let Vue
class Store {
// 构造函数
constructor(options) {
// 处理getters中的fn,使它的变成响应式对象。
// 因为我们的页面中是通过$store.getters.fnName访问,所以它是一个响应式对象
// 仿源码,采用computed计算属性。计算属性的优点:当数据变化时才会更新,具有缓存性
this._wrappedGetters = options.getters
this.getters = Object.create(null)
let computed = Object.create(null)
const store = this
Object.keys(this._wrappedGetters).forEach(key => {
const fn = store._wrappedGetters[key]
// computed: { doubleCounter() {} }是个无参数的形式,所以我们需要对其进行封装
computed[key] = function() {
return fn(store.state)
}
Object.defineProperty(this.getters, key, {
enumerable: true,
get() {
// getters中的属性是function对象,我们访问的是值,所以需处理接收它的返回值
// 在Vue中,computed的值我们可以直接通过Vue.xxx访问,所以store._v[key]可以访问到对应的值(方法)
return '天王盖地虎doubleCounter: ' + store._vm[key]
}
})
})
// 将store转化为响应式对象
// Vue.util.defineReactive可以定义一个响应式对象.但是这里不建议使用defineReactive,因为该方法已经写死,不够灵活。
// 借鸡生蛋,我们采用new Vue的方式生成响应式对象。
// 为了保证数据的安全性-用户不能直接访问到我们的store数据,所以我们采用代理的形式,定义get,set方法。
this._vm = new Vue({
data: {
$$state: options.state
},
computed
})
this._mutations = options.mutations
this._actions = options.actions
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
commit(type, payload) {
const entry = this._mutations[type]
if (!entry) {
console.error('unkown mutation type')
}
entry(this.state, payload)
}
dispatch(type, payload) {
const entry = this._actions[type]
if (!entry) {
console.error('unkown action type')
}
entry(this, payload)
}
get state() {
return this._vm._data.$$state
}
set state(v) {
console.error('please use replaceState to reset state')
}
}
function install(_Vue) {
Vue = _Vue
// 1.挂载$router。
// 由于install会由use调用,调用时,VueRoter对象可能会创建,所以使用全局混入
// VueRouter由根实例(app.vue中的vue对象)传入
// 全局混入: 混入能够增加组件方法的复用性
Vue.mixin({
beforeCreate() {
// 此钩子函数在每个组件创建之前都会执行
// this.$options.router只有根组件存在
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
}
export default { Store, install }
这是一篇学习笔记,可能有的地方描述的不是很准确,欢迎大家指出和讨论~~!