编写vuex(万字核心原理)
一、简易版
1. 文件结构
vuex
-
index.js --- 功能汇总 合并导出
-
install.js --- 插件注册
-
store.js --- 核心功能
-
module --- 模块化核心
-
- module-collection.js --- 模块收集
- module.js --- module构造函数 用于创建module
store
- index.js --- 1 Vue.use(Vuex) 2 new Vuex.store创建store实例
main.js
- 引用store,注册到Vue实例上
util.js
- 遍历方法,遍历对象中的属性,将属性和key传递给回调函数
export const mergeForEach = (obj, fn) => {
if (typeof obj === 'object' && obj != null && typeof fn === 'function') {
Object.keys(obj).forEach((key) => {
fn(obj[key], key)
})
}
}
2. Vuex/index.js
import install from './install';
import Store from './store'
export default{
install,
Store,
//...
}
3. install.js
export let Vue; // 同步导出一份
function install (_Vue) {
Vue = _Vue;
Vue.mixin({ // 借用Vue的mixin方法以及生命周期钩子,合并选项和初始化数据
beforeCreate () {
// 获取根组件上的store 将他共享给每个组件 每个组件中都应该有$store
let options = this.$options;
if (options.store) {
this.$store = options.store
} else {
// 先保证他是一个子组件,并且父亲上有$store
if (this.$parent && this.$parent.$store) {
this.$store = this.$parent.$store
}
}
}
})
}
export default install
4. store.js
import { Vue } from './install'; // install时接收的vue,顺便导出一份,确保调用的是同一个
class Store {
constructor(options) {
// new Store 初始化时传入的数据选项
const { state, mutations, actions, getters } = options
this.getters = {} // 初始化getters
this.mutations = {} // 初始化mutations
this.actions = {} // 初始化actions
const computed = {} // 初始化计算属性,用于关联getter
// 合并getter数据
mergeForEach(getters, (fn, key) => {
// 将getters上的属性同步一份给computed,接收的state就是当前实例的state
computed[key] = () => {
return fn(this.state)
}
// 从getter上面取值的时候,劫持到computed上
Object.defineProperty(this.getters, key, {
get: () => this._vm[key] // 这里的this和vue实例是同一个
})
})
// 合并mutations数据,订阅事件
mergeForEach(mutations, (fn, key) => {
this.mutations[key] = (payload) => {
return fn.call(this, this.state, payload) // 确保this指向
}
})
// 合并actions数据
mergeForEach(actions, (fn, key) => {
this.actions[key] = (payload) => {
// call的第二个this是当前store实例,上面有commit方法 state等
return fn.call(this, this, this.state)
}
})
// 通过vue实例作为响应式桥梁,store数据变化后,能触发watcher更新视图
this._vm = new Vue({
data: {
// $开头的属性会被vue内部合并在_vm上,表示内部属性
$$state: state // state结束vue的响应式数据,访问state时指向$$state
},
computed // 让getter借助vue的计算属性watcher
})
}
// 当访问store时
get state () {
return this._vm._data.$$state // 从vm上面取值
}
// commit方法 发布事件,(用箭头函数不用clll其this指向)
commit = (type, payload) => {
this.mutations[type](payload)
}
// 触发actions方法
dispatch = (type, payload) => {
this.actions[type](payload)
}
}
export default Store
5. store/index.js
import Vue from 'vue'
import Vuex from '@/vuex' // 用自己的vuex
Vue.use(Vuex) // 注册插件 调用install方法
export default new Vuex.Store({ // vuex持久化插件?
state: {
name: 'zhufeng',
age: 12
},
mutations: { // method commit 同步更改状态
changeAge (state, payload) {
state.age = payload
}
},
actions: { // 异步操作 调用api接口 dispatch, 多次commit mutation
changeAge ({ commit }, payload) {
setTimeout(() => {
commit('changeAge', payload);
}, 1000);
}
},
getters: { // 计算属性
myAge (state) {
return state.age 10
}
},
})
6. main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
let vm = new Vue({
name: 'root',
store, // 此store的目的是让所有组件都能访问到store对象
render: h => h(App)
}).$mount('#app')
二、完整版
1. ModuleCollection & Module
ModuleCollection --- 将modules转换为tree
Module --- module构造类
Store
class Store {
constructor(options) {
// 对用户的模块进行整合 当前格式化完毕的数据 放到了this._modules里
this._modules = new ModuleCollection(options); // 对用户的参数进行格式化操作
// ……
}
}
ModuleCollection
import { mergeForEach } from '../util'
import Module from './module';
class ModuleCollection {
constructor(options) {
this.root = null; // 对数据进行格式化操作 初始化根节点
this.register([], options); // [] 为了记录父子关系 用队列方式
}
register (path, rawModule) {
let newModule = new Module(rawModule) // 创建module实例,包含_raw _children state等
if (path.length == 0) {
this.root = newModule // 判断为根节点
} else {
// 根据当前注册的key ,将他注册到对应的模块的儿子处
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo.getChild(current)
}, this.root);
parent.addChild(path[path.length - 1], newModule);
}
// 注册完毕当前模块,在进行注册根模块
if (rawModule.modules) {
mergeForEach(rawModule.modules, (module, key) => {
this.register(path.concat(key), module);
})
}
}
}
export default ModuleCollection
Module
import { mergeForEach } from '../util'
class Module {
constructor(rawModule) {
this._raw = rawModule; // 初始化raw
this._children = {}; // 初始化children
this.state = rawModule.state // 初始化state
}
// 根据name获取children
getChild (childName) {
return this._children[childName]
}
// 添加children
addChild (childName, module) {
this._children[childName] = module
}
// 遍历getters
forEachGetter (cb) {
this._raw.getters && mergeForEach(this._raw.getters, cb)
}
// 遍历Mutations
forEachMutation (cb) {
this._raw.mutations && mergeForEach(this._raw.mutations, cb)
}
// 遍历actions
forEachAction (cb) {
this._raw.actions && mergeForEach(this._raw.actions, cb)
}
// 遍历childrens
forEachChildren (cb) {
this._children && mergeForEach(this._children, cb)
}
// 用于标识他自己是否写了namesapced
get namespaced () {
return !!this._raw.namespaced // module.namespaced直接走这里
}
}
export default Module
2. installModule
根据root中的module初始化options,installModule
Store中
constructor(options) {
// ……
// 此时_module中有root树,包含_raw _child state, _raw中包含了所有属性
this.wrapperGetters = {} // 关联module的_raw.getter
this.mutations = {}; // 初始化mutations
this.actions = {}; // 初始化actions
this._subscribes = []; // 订阅事件
this._committing = false; // 判断是否在mutation中更改state,默认false
this.strict = options.strict; // 开启严格模式,此时如果直接更改state需要抛出错误
let state = options.state; // 初始化状态
installModule(this, state, [], this._modules.root);
resetVM(this, state); // 每次需要重置借用的Vue数据劫持和getter 确保新增的属性有效
// ……
}
getNewState
// 获取最新的state 可能replaceState将数据变了,但store还是绑定的之前的数据
function getNewState (store, path) {
// 通过pact获取最新state 有可能数据变了 但是 'a/b/name' 无法取到
return path.reduce((memo, current) => {
return memo[current];
}, store.state)
}
store._withCommittting
// 将每个mutation都用该函数包裹,只要通过mutation修改state就一定会触发该函数
// 然后将_committing改为true,再执行mutation,执行完重置,这样就能监听到是否通过mutation修改state
_withCommittting (fn) {
this._committing = true; // 如果true
fn(); // 函数是同步的 获取_commiting 就是true,如果是异步的那么就会变成false 就会打印日志
this._committing = false;
}
installModule
function installModule (store, rootState, path, module) {
// 没有namespace的时候 getters都放在根上,actions,mutations 会被合并数组
let ns = store._modules.getNamespace(path); // _modules == moduleCollection类的实例
// module.state => 放到rootState对应的儿子里
if (path.length > 0) {
// 儿子模块 需要找到对应父模块,将状态声明上去
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current];
}, rootState);
// _withCommittting函数用于配合判断是否在mutation中更改state
store._withCommittting(() => {
// 对象新增属性不能导致重新更新视图,需要借用Vue的数据劫持
Vue.set(parent, path[path.length - 1], module.state);
})
}
// 初始化getters
module.forEachGetter((fn, key) => {
key = ns key // namespace为true时,命名空间成了 a/b/name,需用相应的格式访问
store.wrapperGetters[key] = function () {
// 调用getter,改变this指向,向其传递最新的数据
return fn.call(store, getNewState(store, path));
}
});
// 初始化mutations
module.forEachMutation((fn, key) => {
key = ns key // namespace为true时,命名空间成了 a/b/name,需用相应的格式访问
store.mutations[key] = store.mutations[key] || []; // 如果不是数组则需要转为数组
store.mutations[key].push((payload) => {
store._withCommittting(() => {
fn.call(store, getNewState(store, path), payload); // 调用mutations
})
// 先调用mutation 在执行subscirbe
store._subscribes.forEach(fn => fn({ type: key, payload }, store.state));
})
});
// 初始化actions
module.forEachAction((fn, key) => {
key = ns key // namespace为true时,命名空间成了 a/b/name,需用相应的格式访问
store.actions[key] = store.actions[key] || [];
store.actions[key].push((payload) => {
return fn.call(store, store, payload) // 调用actions
})
});
// 初始化children
module.forEachChildren((child, key) => {
installModule(store, rootState, path.concat(key), child);
});
}
reaetVM
function resetVM (store, state) {
let oldVm = store._vm; // 保存一份旧的实例 新的绑定成功后需要把旧的销毁
store.getters = {}; // store的getters,关联vm上的computed
const computed = {}; // vm的计算属性,这里需要借用,以实现响应式数据
// installModule时将getters都绑定在了warpperGetter上,这里需要同步一份给computed
forEach(store.wrapperGetters, (getter, key) => {
computed[key] = getter; // 同步一份给computed
Object.defineProperty(store.getters, key, {
get: () => store._vm[key] // 访问store.getters时,代理到vm的computed
})
});
// 借用Vue实例,实现响应式
store._vm = new Vue({
data: {
$$state: state // $开头的属性会被Vue识别为内部属性,只能通过vm访问
},
computed // 同步了getters的计算属性,访问getters时 其实就在访问computed
});
if (store.strict) { // 说明是严格模式我要监控状态
// 通过Vue的$watch监听$$state变化,$$state就是store的state
store._vm.$watch(() => store._vm._data.$$state, () => {
console.assert(store._committing, 'no mutate in mutation handler outside')
// 我希望状态变化后直接就能监控到,这里需要同步执行才能获取
// 而watcher都是异步的,所以sync需设置为true,同时层级问题需要deep递归
}, { deep: true, sync: true });
}
if (oldVm) { // 重新创建实例后,需要将老的实例卸载掉
Vue.nextTick(() => oldVm.$destroy())
}
}
3. namespaced
namespaced 能解决子模块和父模块的命名冲突文件 ,相当于增加了一个命名空间
如果没有namespaced 默认getters都会被定义到父模块上
mutations 会被合并在一起, 最终一起调用,有了命名空间就没有这个问题了
需要注意的是,子模块的名字不能和父模块中的状态重名,否则会覆盖
使用时 --- 入口
new Vuex.Store({
namespaced: true, // 开启命名空间
state: {},
mutations: {}
})
ModuleCollection类中
class ModuleCollection {
constructor(options) {
// ……
this.register([], options); // [] 为了记录父子关系 用队列方式
}
register (path, rawModule) {
// 传入的rawModule就是用户的options
let newModule = new Module(rawModule) // 创建module实例,包含_raw _children state等
// ……
}
}
Module类中
class Module {
constructor(rawModule) {
this._raw = rawModule; // 传入的options
// ……
}
// ……
// 用于标识他自己是否写了namesapced
get namespaced () {
return !!this._raw.namespaced // module.namespaced直接走这里
}
}
getNamespace
// ModuleCollection的原型方法
getNamespace (path) { // 返回一个字符串 a/b/c ''
let root = this.root
let ns = path.reduce((ns, key) => { // this.root.c.namespace
let module = root.getChild(key); // module上的getChild方法,返回匹配的儿子
root = module; // 重置root 用于下一次迭代继续查找儿子
// namespaced则开启了命名空间,需用 obj['a/b/name'] 方式访问
return module.namespaced ? ns key '/' : ns
}, '');
return ns;
}
使用命名空间前缀key
function installModule (store, rootState, path, module) {
let ns = store._modules.getNamespace(path); // _modules == moduleCollection类的实例
// ……
module.forEachGetter((fn, key) => {
key = ns key // namespace为true时,命名空间成了 a/b/name,需用相应的格式访问
store.wrapperGetters[key] = function () {
// 调用getter,改变this指向,向其传递最新的数据
return fn.call(store, getNewState(store, path));
}
});
// ……
}
4. logger
plugin插件 --- 通过plugins注入一个函数,该函数中会接收store实例,基于store实例进行扩展
依赖方法 store.subscribe,每次通过mutations修改state时都会调用该方法
使用时 --- 入口
new Vuex.Store({
plugins: [
logger() // 使用日志插件
],
state: {},
mutations: {}
})
store.subscribe
subscribe (fn) {
this._subscribes.push(fn); // 收集订阅事件
}
初始化mutations时
module.forEachMutation((fn, key) => {
key = ns key // namespace为true时,命名空间成了 a/b/name,需用相应的格式访问
store.mutations[key] = store.mutations[key] || []; // 如果不是数组则需要转为数组
store.mutations[key].push((payload) => {
store._withCommittting(() => {
fn.call(store, getNewState(store, path), payload); // 调用mutations
})
// 先调用mutation 在执行subscirbe --- 调用完mutation后 在这里调用subscribe
store._subscribes.forEach(fn => fn({ type: key, payload }, store.state));
})
});
logger
function logger () {
return function (store) {
// 克隆一份,否则打印的是同一个,可以用lodash的deepClone
let prevState = JSON.stringify(store.state);
// 所有的更新操作都基于mutation (状态变化都是通过mutation的)
store.subscribe((mutation, state) => {
// 如果直接手动的更改状态 此scbscribe是不会执行 commit()
console.log('prevState:' prevState); // 旧的
console.log('mutation:' JSON.stringify(mutation)); // 执行的哪个
console.log('currentState:' JSON.stringify(state)); // 新的
prevState = JSON.stringify(state); // 重置旧的
})
}
}
5. persistent 依赖方法 replaceState
插件,持久化,核心是使用浏览器缓存 store.reolaceState
使用时 --- 入口
new Vuex.Store({
plugins: [
persists() // 使用持久化插件
],
state: {},
mutations: {}
})
store.replaceState
replaceState (newState) { // 需要替换的状态
this._withCommittting(() => {
this._vm._data.$$state = newState; // 替换最新的状态, 赋予对象类型会被重新劫持
})
// 虽然替换了状态,但是mutation getter中的state在初始化的时候 已经被绑定死了老的状态
// 所以需要用到getNewState方法,从vm上获取最新的数据,对store进行绑定
}
presisted --- 插件核心
function persists () {
return function (store) { // vuex-persists
let localState = JSON.parse(localStorage.getItem('VUEX:STATE'))
if (localState) {
store.replaceState(localState); // 替换state
}
// 和mutation挂钩 状态变化时 需要更新浏览器缓存,触发mutation就一定会触发subscribe
store.subscribe((mutation, rootState) => {
// 状态发生变化就存localStorage中,这里可以用队列 nextTick做防抖处理
localStorage.setItem('VUEX:STATE', JSON.stringify(rootState));
});
}
}
6. registerModule
store原型方法,动态插入一个新的store-module
使用时 --- 入口
store.registerModule('a/newModule', {
state: {},
mutations: {}
})
store.registerModule核心方法
registerModule = (path, module) => {
if (typeof path == 'string') path = [path]; // 确保最终都转换成数组 register(['a','c'])
this._modules.register(path, module); // 模块的注册, 将用户给的数据放到树中
// 注册完毕后 将用户的module 重新安装
installModule(this, this.state, path, module.newModule);
// vuex内部重新注册的话 会重新生成实例, 虽然重新安装了 ,只解决了状态的问题,但是computed就丢失了
resetVM(this, this.state); // 销毁重来
}
7. strict
开启严格模式,执行一些限制规则
例如只能通过mutations修改state,否则抛出错误
使用时 --- 入口
new Vuex.Store({
strict: true,
state: {}
})
Store构造函数中初始化状态
class Store{
constructor(options) {
this.strict = options.strict; // 开启严格模式,此时如果直接更改state需要抛出错误
// ……
}
}
vm.$watch
resetVM方法中,借用vm.$watch监听store关联的vm实例数据$$store
数据变化时,如果_committing状态为false则说明其没有通过mutations修改state,此时抛出错误
关于_committing可回顾前面的代码
function resetVM (store, state) {
if (store.strict) { // 说明是严格模式我要监控状态
// 通过Vue的$watch监听$$state变化,$$state就是store的state
store._vm.$watch(() => store._vm._data.$$state, () => {
// console.assert可查阅MDN,当第一个参数为false时打印第二个参数内容
console.assert(store._committing, 'no mutate in mutation handler outside')
// 我希望状态变化后直接就能监控到,这里需要同步执行才能获取
// 而watcher都是异步的,所以sync需设置为true,同时层级问题需要deep递归
}, { deep: true, sync: true });
}
}
8. 辅助函数
mapMutations mapActions mapGetters mapState
可通过...mapActions的方式结构出来使用
mapState
export function mapState (stateList) {
let obj = {};
for (let i = 0; i < stateList.length; i ) {
let stateName = stateList[i];
obj[stateName] = function () {
return this.$store.state[stateName];
};
}
return obj;
}
mapMutations
export function mapMutations (mutationList) {
let obj = {};
for (let i = 0; i < mutationList.length; i ) {
obj[mutationList[i]] = function (payload) {
this.$store.commit(mutationList[i], payload);
};
}
return obj;
}
mapActions
export function mapActions (actionList) {
let obj = {};
for (let i = 0; i < actionList.length; i ) {
obj[actionList[i]] = function (payload) {
this.$store.dispatch(actionList[i], payload);
};
}
return obj;
}
mapGetters
export function mapGetters (gettersList) {
let obj = {};
for (let i = 0; i < gettersList.length; i ) {
let getterName = gettersList[i];
obj[getterName] = function () {
return this.$store.getters[getterName];
};
}
return obj;
}
三、总结
- 核心:install Store ModuleCollection Module installModule resetVm
- 辅助方法:getNewState mergeForEach
- 插件方法:logger presisted
- 辅助函数:mapState mapGetters mapMutations mapActions
install
核心原理:
借用Vue的mixin混入生命周期钩子beforeCreate
在该钩子中将传入的store挂载到this.store上,如果是子组件就取父组件的store上,如果是子组件就取父组件的store
Store
核心原理:
初始化状态,解析用户传入的modulds,调用installModule方法对每一个module进行安装
借用Vue的数据劫持和computed,实现state和getters,通过resetVm方法将数据关联起来
提供几个核心方法:
reolaceState 整体替换state,直接改写借用的vm._data.$$state
commit 遍历执行mutations
dispatch 遍历执行actions
registerModule 注入一个新的module,核心是利用_module.register合并module,然后installModule安装,最后再用resetVM重置getters和劫持的数据
_withCommitting 用于切片mutations,调用mutations时对状态进行监听
subscribe 订阅执行发布的事件,用于扩展插件使用
ModuleCollection
核心原理:
通过register方法递归合并一个module树,module树由Module构造类产生
额外提供一个可返回命名空间字符串key的方法 getNamespace
Module
核心原理:
将用户传入的options转变为children state _raw格式的数据
额外提供一些遍历options上的getters、actions等的方法
installModule
核心原理:
递归解析store._module,进行安装,借助module提供的遍历方法,向store中赋值
resetVm
核心原理:
借助Vue的响应式数据实现state,computed实现getters
借助$watch监听state变化,执行严格模式下的错误收集
初始化完成后,借助vue生命周期钩子销毁实例
另外还有 createNamespacedHelpers方法 用于寻找命名空间中的map
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgbaijc
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13