Vuex源码实现
1. Vuex核心概念State,Getters,Mutations, Actions, Modules
State 存放数据,(响应式:数据改变,视图也改变)
age: 18
Getters 相当于Vue中计算属性,具有缓存机制
changeAge:state=>{ return state.age + 2}
// 将age+2Mutations 修改数据(同步)
addAge: (state, data) => { state.age += data }
@click="$store.commit('addAge', 5)"
Actions 修改数据(异步)
// actions:{} awaitAddAge: ({ commit }, data) => { setTimeout(() => { commit('addAge', data) }, 2000); }
// 页面使用 延迟两秒执行 @click="$store.dispatch('awaitAddAge', 2)"
- Modules
Modules模块化可以理解为Store里嵌套Store
2. 手写Vuex分析
分析:
Vuex首先用到了单例模式和发布订阅者模式两种设计思想
- 单例模式,即Vuex的四个核心概念都在一个Store实例中完成
- 发布订阅者模式:即mutations同步修改方法,和actions异步修改方法通过发布订阅者模式来实现state数据的修改。
Vuex是个插件,通过vue.use产生关联(一般通过vue.use注册插件)
Vue.use(fnc) 执行方法
function ac() { console.log(1000); } Vue.use(ac) // 打印1000
如果这个方法中有一个install这个属性(方法),会执行install
function ac() { console.log(1000); } ac.install = function () { console.log(300); } Vue.use(ac) Vue.use(Vuex); // 打印300,1000不打印
如果install有参数,第一个参数就是Vue的实例
function ac() { console.log(1000); } ac.install = function (_Vue) { console.log(_Vue); } Vue.use(ac) // 打印出Vue的实例
每一个使用的组件中都有store容器
Mutations通过Commit方法直接修改State
Actions通过Dispatch触发Commit执行Mutations方法
Vuex是个单线程的方法
写的每个方法要在每一个组件中使用,需要把方法放在根实例下。
组件的渲染关系是父子关系
3. 创建Vuex入口文件
在src目录下创建Vuex文件夹并新建index.js(Vuex入口文件)(替换Vuex文件)
// 入口文件 import { Store, install } from './install' export default { Store, // 容器 install // 注册插件,与Vue产生关联 }
创建install.js文件
export class Store { } export const install = function () { console.log(200); }
替换官方Vuex为新建的Vuex插件,修改store/index.js
// old import Vuex from "vuex"; // new 修改为 import Vuex from "../Vuex"; // 自定义Vuex组件
刷新页面,打印了200
通过store/index.js Vue.use(Vuex) 调用install方法,执行install.js文件里的
console.log(200);
,打印出200
4. 将store放到每个实例上
获取Vue实例并把store放到每一个组件实例上
// 实现 store 放到每一使用的组件中 export const install = function (_Vue) { Vue = _Vue // 获取Vue实例,以便使用Vue实例上的方法 // 使用Vue提供的方法 Vue.mixin({}) // Vue.mixin({})方法,混入方法和数据 Vue.mixin({ // mixin 混入生命周期beforeCreate beforeCreate () { let options = this.$options if (options.store) { // 根实例,只有根实例才有store // 给根实例添加一个$store属性 this.$store = options.store } else { // 其他实例,除根实例外 // 判断有没有父亲实例,如果有拿到父亲实例上的$store // 从根实例开始,逐步从根实例传到子组件实例上 this.$store = this.$parent && this.$parent.$store } // console.log(this.$store); } }) }
设置Store容器
export class Store { // Store容器 constructor(options){ // options为用户配置项(包括state,getters,mutations等等) console.log(options); } }
初始化Vuex.Store
export default new Vuex.Store({ state: state, // eslint-disable-next-line getters: getters, mutations: mutations, actions: actions, modules: {}, });
取值
$store.state.age
= 33 // 成功获取修改
@click="$store.state.age = 66"
// 页面未刷新,但是实例上age已变成66,也就是state.age未实现响应式。
5. 处理Vuex响应式
处理Vuex响应式
this.state = options.state
处理不能实现响应式处理Vuexstate响应式,用Vue的数据劫持来实现
将state上的所有属性代理到_vm实例上, 然后在get时,返回代理的_vm实例
export class Store { // Store容器 constructor(options){ // options为用户配置项(包括state,getters,mutations等等) // this.state = options.state // 不能实现响应式 this._vm = new Vue({ // 通过Vue实例来对state进行代理 data: { state: options.state } }) // Object.defineProperty() // Object.defineProperty方法可以在类里进行劫持 } get state() { return this._vm.state // 返回实现代理的_vm实例 } }
6. 处理计算属性getters
- 获取options里的getters内容
- 如果getters里有多个方法需遍历
- 通过Object.defineProperty()方法对getters方法进行劫持
// getters 计算属性,用户 let getters = options.getters this.getters = {} console.log(getters['getAge'](this.state)); // 当getters里有多个方法时,对方法进行遍历 Object.keys(getters).forEach(key => { // 如果this.getters没有getters方法,则进行添加 Object.defineProperty(this.getters,key,{ get: () => { console.log(getters[key]); console.log(this.state); // getters[key] 获取到到是getters里到方法, // 通过getters[key](this.state)传入this.state调用这个方法 return getters[key](this.state) } }) })
7. 创建方法文件夹utils
创建utils文件夹,并新建index.js文件
新建第一个方法foreach
export function foreach(obj, cb){ Object.keys(obj).forEach(key => { cb(key, obj[key]) }) }
导入foreach方法实现getters
// 调用foreach方法实现getters foreach(getters,(key, value) => { Object.defineProperty(this.getters,key,{ get: () => { // console.log(getters[key]); // console.log(this.state); // getters[key] 获取到到是getters里到方法, // 通过getters[key](this.state)传入this.state调用这个方法 return value(this.state) } }) })
8. 设置getters缓存机制
通过Vue的计算属性来代理实现getters方法的缓存机制
在Vue中,state响应式和getters缓存机制都是靠初始化一个Vue实例来实现,响应式通过Vue实例的data数据劫持来实现,getters缓存机制通过Vue实例的计算属性computed来实现。
let computed = {} // 定义一个computed对象 // 在foreach方法中添加computed方法 computed[key] = () => { return value(this.state) } // 通过Object.defineProperty()方法的get属性返回当前Vue实例_vm Object.defineProperty(this.getters,key,{ get: () => { // console.log(getters[key]); // console.log(this.state); // getters[key] 获取到到是getters里到方法, // 通过getters[key](this.state)传入this.state调用这个方法 console.log(this._vm); return this._vm[key] // return value(this.state) } }) // 在初始化Vue实例时,代理computed this._vm = new Vue({ // 通过Vue实例来对state进行劫持 data: { state: options.state }, computed // ES6中属性和属性值相同可以直接省略 computed = computed:'computed' })
9. mutations方法实现
首先明确知道 mutations和actions 是个发布订阅者模式
在Store类的constructor构造方法里初始化mutations方法
// 获取store里设置的mutations方法 let mutations = options.mutations // 初始化一个mutations空对象 this.mutations = {} // 遍历Store里设置的mutations方法 foreach(mutations, (key, value) => { this.mutations[key] = (data) => { value(this.state, data) } })
在Store类上通过commit方法调用mutations方法
可以理解为通过commit方法专门来调用对应的mutations方法
commit = (name, data) => { // this 永远指向store,当前的实例 this.mutations[name](data) }
10. actions方法实现
actions方法与mutations方法基本一致,只有在Store类里设置actions方法时,传入方法的第一个参数是this,也就是当前Store这个类的实例。
在Store类的constructor构造方法里初始化actions方法
// 获取用户设置的acitons异步修改方法 let actions = options.actions // 初始化一个空的actions对象 this.actions = {} // 遍历用户设置的acitons方法并初始化执行方法 foreach(actions, (key, value) => { this.actions[key] = (data) => { value(this, data) // 此处应传入this即Store类这个实例 } })
在Store类上通过dispatch方法调用actions方法
// actions异步修改数据方法 dispatch = (name, data) => { this.actions[name](data) }
dispatch方法和commit方法在使用时有差别
// commit同步修改方法在使用时第一个参数直接传入当前Store的state addAge: (state, data) => { state.age += data } // dispatch异步修改方法在使用时第一个参数传入的是Store这个实例,dispatch在使用时会从Store实例中解构出commit同步修改方法 awaitAddAge: ({ commit }, data) => { setTimeout(() => { commit('addAge', data) }, 2000); }
11. modules模块化实现
- modules模块化处理需要格式化数据
- 格式化数据 变成一个树形结构,也就是一个对象
// 页面结构 export default new Vuex.Store({ state: state, // eslint-disable-next-line getters: getters, mutations: mutations, actions: actions, // 模块 {mutations: []} modules: { a: { state:{ age: 200 }, mutations:{ addAge: (state, data) => { state.age += data } } }, b : { state:{ age: 112 }, mutations:{ addAge: (state, data) => { state.age += data } } } }, }); // 转换成的结构 root = { _raw: '用户传过来的数据', _children:{ a: { _raw: 'a用户传过来的数据', _children:{ }, state: '数据' } b: { _raw: 'b用户传过来的数据', _children:{ }, state: '数据' } }, state: '根数据' }