这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战
TIP 👉 休对故人思故国,且将新火试新茶。诗酒趁年华。____苏轼《望江南·超然台作》
前言
Vuex 原理
vuex 核心原理分析
我们不直接硬生生的去分析源码,现在希望大家抛掉对 vuex 的所有记忆,让我们回到几年前vuex诞生的那个时间点,从头开始去思考基于 FLUX 思想,如何打造一个我们自己的状态管理工具
a. 站在FLUX 的角度去思考
在开发中面临最多的场景是状态重复但是不集中
// a.vue
<h1>{{ username }}</h1>
// b.vue
<h2>
{{ username }}
</h2>
/**
* 如果 username 需要在每个组件都获取一次,是不是很麻烦,虽然可以通过共同的父级传入,但是不都是这种理想情况
*/
这里其实就出现了状态重复的问题,在不同的组件中依赖了同样的状态,重复就会导致不对等的风险,基于 FLUX 的思想,我们设计的状态管理将是中心化的工具,也就是集中式存储管理应用的所有组件的状态,大白话,就是将所有的状态放在一个全局的 Tree 结构中,集中放在一起的好处是可以有效避免重复的问题,也更好的管理,将状态和视图层解耦
TJ 原来说过我的状态管理就是 {},对中心化的管理工具来说,不就是这样嘛😂
b. 如何去更改状态
自由即代表了混乱,FLUX 推崇以一种可预测的方式发生变化,而且有且唯一一种,这样的好处是所有的行为可预测,可测试,对于之后做个 dev-tool 去调试、时间旅行都很方便,现在的问题就是要去思考同步和异步的问题了,为了区分的更清楚,我们定义两种行为,Actions 用来处理异步状态变更(内部还是调用 Mutations),Mutations 处理同步的状态变更,整个链路应该是一个闭环,单向的,完美契合 FLUX 的思想
「页面 dispatch/commit」-> 「actions/mutations」-> 「状态变更」-> 「页面更新」-> 「页面 dispatch/commit」...
c. 如何和 vue 集成呢?
插件呀~这样可以和 vue 集成在一起,通过 mixin 将 $store 这样的快速访问 store 的快捷属性注入到每一个 vue 实例中
d. 怎么让 store 是响应式的呢?
利用 vue data 里的状态是响应式的啊~
e. 开始撸码
Step1 - store 注册
/**
* store.js - store 注册
*/
let Vue
// vue 插件必须要这个 install 函数
export function install(_Vue) {
// 拿到 Vue 的构造器,存起来
Vue = _Vue
// 通过 mixin 注入到每一个vue实例 👉 https://cn.vuejs.org/v2/guide/mixins.html
Vue.mixin({ beforeCreate: vuexInit })
function vuexInit () {
const options = this.$options
// 这样就可以通过 this.$store 访问到 Vuex 实例,拿到 store 了
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
Step2 - 响应式
/**
* store.js - 实现响应式
*/
export class Store {
constructor(options = {}) {
resetStoreVM(this, options.state)
}
get state () {
return this._vm._data.$$state
}
}
function resetStoreVM(store, state) {
// 因为 vue 实例的 data 是响应式的,正好利用这一点,就可以实现 state 的响应式
store._vm = new Vue({
data: {
$$state: state
}
})
}
Step3 - 衍生数据
/**
* store.js - 衍生数据(getters)
*/
export class Store {
constructor(options = {}) {
const state = options.state
resetStoreVM(this, state)
// 我们用 getters 来收集衍生数据 computed
this.getters = {}
// 简单处理一下,衍生不就是计算一下嘛,传人 state
_.forEach(this.getters, (name, getterFn) => {
Object.defineProperty(this.getters, name, {
get: () => getterFn(this.state)
})
})
}
get state () {
return this._vm._data.$$state
}
}
function resetStoreVM(store, state) {
store._vm = new Vue({
data: {
$$state: state
}
})
}
Step4 - Actions/Mutations
/**
* store.js - Actions/Mutations 行为改变数据
*/
export class Store {
constructor(options = {}) {
const state = options.state
resetStoreVM(this, state)
this.getters = {}
_.forEach(options.getters, (name, getterFn) => {
Object.defineProperty(this.getters, name, {
get: () => getterFn(this.state)
})
})
// 定义的行为,分别对应异步和同步行为处理
this.actions = {}
this.mutations = {}
_.forEach(options.mutations, (name, mutation) => {
this.mutations[name] = payload => {
// 最终执行的就是 this._vm_data.$$state.xxx = xxx 这种操作
mutation(this.state, payload)
}
})
_.forEach(options.actions, (name, action) => {
this.actions[name] = payload => {
// action 专注于处理异步,这里传入 this,这样就可以在异步里面通过 commit 触发 mutation 同步数据变化了
action(this, payload)
}
})
}
// 触发 mutation 的方式固定是 commit
commit(type, payload) {
this.mutations[type](payload)
}
// 触发 action 的方式固定是 dispatch
dispatch(type, payload) {
this.actions[type](payload)
}
get state () {
return this._vm._data.$$state
}
}
function resetStoreVM(store, state) {
store._vm = new Vue({
data: {
$$state: state
}
})
}
Step5 - 分形,拆分出多个 Module
// module 可以对状态模型进行分层,每个 module 又含有自己的 state、getters、actions 等
// 定义一个 module 基类
class Module {
constructor(rawModule) {
this.state = rawModule || {}
this._rawModule = rawModule
this._children = {}
}
getChild (key) {
return this._children[key]
}
addChild (key, module) {
this._children[key] = module
}
}
// module-collection.js 把 module 收集起来
class ModuleCollection {
constructor(options = {}) {
this.register([], options)
}
register(path, rawModule) {
const newModule = new Module(rawModule)
if (path.length === 0 ) {
// 如果是根模块 将这个模块挂在到根实例上
this.root = newModule
}
else {
const parent = path.slice(0, -1).reduce((module, key) => {
return module.getChild(key)
}, this.root)
parent.addChild(path[path.length - 1], newModule)
}
// 如果有 modules,开始递归注册一波
if (rawModule.modules) {
_.forEach(rawModule.modules, (key, rawChildModule) => {
this.register(path.concat(key), rawChildModule)
})
}
}
}
// store.js 中
export class Store {
constructor(options = {}) {
// 其余代码...
// 所有的 modules 注册进来
this._modules = new ModuleCollection(options)
// 但是这些 modules 中的 actions, mutations, getters 都没有注册,所以我们原来的方法要重新写一下
// 递归的去注册一下就行了,这里抽离一个方法出来实现
installModule(this, this.state, [], this._modules.root);
}
}
function installModule(store, state, path, root) {
// getters
const getters = root._rawModule.getters
if (getters) {
_.forEach(getters, (name, getterFn) => {
Object.defineProperty(store.getters, name, {
get: () => getterFn(root.state)
})
})
}
// mutations
const mutations = root._rawModule.mutations
if (mutations) {
_.forEach(mutations, (name, mutation) => {
let _mutations = store.mutations[name] || (store.mutations[name] = [])
_mutations.push(payload => {
mutation(root.state, payload)
})
store.mutations[name] = _mutations
})
}
// actions
const actions = root._rawModule.actions
if (actions) {
_.forEach(actions, (name, action) => {
let _actions = store.actions[name] || (store.actions[name] = [])
_actions.push(payload => {
action(store, payload)
})
store.actions[name] = _actions
})
}
// 递归
_.forEach(root._children, (name, childModule) => {
installModule(this, this.state, path.concat(name), childModule)
})
}
Step6 - 插件机制
(options.plugins || []).forEach(plugin => plugin(this))
以上只是以最简化的代码实现了 vuex 核心的 state module actions mutations getters 机制。
「欢迎在评论区讨论」