您好,欢迎来到世娱网。
搜索
您的当前位置:首页vuex实现及简略解析(小结)

vuex实现及简略解析(小结)

来源:世娱网


设置state响应数据

通过上面,我们已经从每个组件都通过this.$store来访问到我们的store的实例,下面我们就编写state数据,让其变成双向绑定的数据。下面我们改写store

class Store {
 constructor (options) {
 let { state, getters, actions, mutations } = options // 拿到传进来的参数
 this.getters = {}
 this.mutations = {}
 this.actions = {}
 // vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图
 this._vm = new Vue({
 data: {
 state
 }
 })
 }
 // 访问state对象时候,就直接返回响应式的数据
 get state() { // Object.defineProperty get 同理
 return this._vm.state
 }
}

传进来的state对象,通过new Vue({data: {state}})的方式,让数据变成响应式的。当访问state对象时候,就直接返回响应式的数据,这样子在App.vue中就可以通过this.$store.state.count拿到state的数据啦,并且是响应式的呢。

编写mutations、actions、getters

上面我们已经设置好state为响应式的数据,这里我们在store.js里面写上mutations、actions、getters,如下

import Vue from 'vue'
import Vuex from './vuex' // 引入我们的自己编写的文件

Vue.use(Vuex) // 安装store
// 实例化store,参数数对象
export default new Vuex.Store({
 state: {
 count : 1000
 },
 getters : {
 newCount (state) {
 return state.count + 100
 }
 },
 mutations: {
 change (state) {
 console.log(state.count)
 state.count += 10
 }
 },
 actions: {
 change ({commit}) {
 // 模拟异步
 setTimeout(() => {
 commit('change')
 }, 1000)
 }
 }
})

配置选项都写好之后,就看到getters对象里面有个newCount函数,mutationsactions对象里面都有个change函数,配置好store之后我们在App.vue就可以写上,dispatchcommit,分别可以触发actionsmutations,代码如下

<template>
 <div id="app">
 这里是store的state------->{{this.$store.state.count}} <br/>
 这里是store的getter------->{{this.$store.getters.newCount}} <br/>
 <button @click="change">点击触发dispach--> actions</button>
 <button @click="change1">点击触发commit---> mutations</button>
 </div>
</template>

<script>
export default {
 name: 'app',
 methods: {
 change () {
 this.$store.dispatch('change') // 触发actions对应的change
 },
 change1 () {
 this.$store.commit('change') // 触发mutations对应的change
 }
 },
 mounted () {
 console.log(this.$store)
 }
}
</script>

数据都配置好之后,我们开始编写store类,在此之前我们先编写一个循环对象工具函数。

const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key]))
// 作用:
// 例如{a: '123'}, 把对象的key和value作为参数
// 然后就是函数运行callback(a, '123')

工具函数都准备好了,之后,下面直接县编写gettersmutationsactions的实现

class Store {
 constructor (options) {
 let { state = {}, getters = {}, actions = {}, mutations = {} } = options
 this.getters = {}
 this.mutations = {}
 this.actions = {}
 // vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图
 this._vm = new Vue({
 data: {
 state
 }
 })
 // 循环getters的对象
 myforEach(getters, (getterName, getterFn) => {
 // 对this.getters对象进行包装,和vue的computed是差不多的
 // 例如 this.getters['newCount'] = fn(state)
 // 执行 this.getters['newCount']()就会返回计算的数据啦
 Object.defineProperty(this.getters, getterName, {
 get: () => getterFn(state)
 })
 })
 // 这里是mutations各个key和值都写到,this.mutations对象上面
 // 执行的时候就是例如:this.mutations['change']()
 myforEach(mutations, (mutationName, mutationsFn) => {
 // this.mutations.change = () => { change(state) }
 this.mutations[mutationName] = () => {
 mutationsFn.call(this, state)
 }
 })
 // 原理同上
 myforEach(actions, (actionName, actionFn) => {
 // this.mutations.change = () => { change(state) }
 this.actions[actionName] = () => {
 actionFn.call(this, this)
 }
 })
 const {commit , dispatch} = this // 先存一份,避免this.commit会覆盖原型上的this.commit
 // 解构 把this绑定好
 // 通过结构的方式也要先调用这类,然后在下面在调用原型的对应函数
 this.commit = type => {
 commit.call(this, type)
 }
 this.dispatch = type => {
 dispatch.call(this, type)
 }
 }
 get state() { // Object.defineProperty 同理
 return this._vm.state
 }
 // commi调用
 commit (type) {
 this.mutations[type]()
 }
 // dispatch调用
 dispatch (type) {
 this.actions[type]()
 }
}

通过上面的,我们可以看出,其实mutationsactions都是把传入的参数,赋值到store实例上的this.mutationsthis.actions对象里面。

当组件中this.$store.commit('change')的时候 其实是调用this.mutations.change(state),就达到了改变数据的效果,actions同理。

getters是通过对Object.defineProperty(this.getters, getterName, {})
对this.getters进行包装当组件中this.$store.getters.newCount其实是调用getters对象里面的newCount(state),然后返回计算结果。就可以显示到界面上了。

大家看看完成后的效果图。

到这里大家应该懂了vuex的内部代码的工作流程了,vuex的一半核心应该在这里了。为什么说一半,因为还有一个核心概念module,也就是vuex的数据的模块化。

vuex数据模块化

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

例如下面的store.js

// 实例化store,参数数对象
export default new Vuex.Store({
 modules: {
 // 模块a
 a: {
 state: {
 count: 4000
 },
 actions: {
 change ({state}) {
 state.count += 21
 }
 },
 modules: {
 // 模块b
 b: {
 state: {
 count: 5000
 }
 }
 }
 }
 },
 state: {
 count : 1000
 },
 getters : {
 newCount (state) {
 return state.count + 100
 }
 },
 mutations: {
 change (state) {
 console.log(state.count)
 state.count += 10
 }
 },
 actions: {
 change ({commit}) {
 // 模拟异步
 setTimeout(() => {
 commit('change')
 }, 1000)
 }
 }
})

然后就可以在界面上就可以写上this.$store.state.a.count(显示a模块count)this.$store.state.a.b.count(显示a模块下,b模块的count),这里还有一个要注意的,其实在组件中调用this.$store.dispatch('change')会同时触发,根的actionsa模块actions里面的change函数。

下面我们就直接去实现models的代码,也就是整个vuex的实现代码,

'use strict'

let Vue = null
const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key]))

class Store {
 constructor (options) {
 let state = options.state
 this.getters = {}
 this.mutations = {}
 this.actions = {}
 // vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图
 this._vm = new Vue({
 data: {
 state
 }
 })

 // 把模块之间的关系进行整理, 自己根据用户参数维护了一个对象
 // root._children => a._children => b
 this.modules = new ModulesCollections(options)
 // 无论子模块还是 孙子模块 ,所有的mutations 都是根上的
 // 安装模块
 installModules(this, state, [], this.modules.root)

 // 解构 把this绑定好
 const {commit , dispatch} = this
 // 通过结构的方式也要先调用这类,然后在下面在调用原型的对应函数
 this.commit = type => {
 commit.call(this, type)
 }
 this.dispatch = type => {
 dispatch.call(this, type)
 }
 }
 get state() { // Object.defineProperty 同理
 return this._vm.state
 }
 commit (type) {
 // 因为是数组,所以要遍历执行
 this.mutations[type].forEach(fn => fn())
 }
 dispatch (type) {
 // 因为是数组,所以要遍历执行
 this.actions[type].forEach(fn => fn())
 }
}

class ModulesCollections {
 constructor (options) { // vuex []
 // 注册模块
 this.register([], options)
 }
 register (path, rawModule) {
 // path 是空数组, rawModule 就是个对象
 let newModule = {
 _raw: rawModule, // 对象
 _children: {}, // 把子模块挂载到这里
 state: rawModule.state
 }
 if (path.length === 0) { // 第一次
 this.root = newModule
 } else {
 // [a, b] ==> [a]
 let parent = path.slice(0, -1).reduce((root, current) => {
 return root._children[current]
 }, this.root)
 parent._children[path[path.length - 1]] = newModule
 }
 if (rawModule.modules) {
 // 遍历注册子模块
 myforEach(rawModule.modules, (childName, module) => {
 this.register(path.concat(childName), module)
 })
 }
 }
}

// rootModule {_raw, _children, state }
function installModules (store, rootState, path, rootModule) {
 // rootState.a = {count:200}
 // rootState.a.b = {count: 3000}
 if (path.length > 0) {
 // 根据path找到对应的父级模块
 // 例如 [a] --> path.slice(0, -1) --> [] 此时a模块的父级模块是跟模块
 // 例如 [a,b] --> path.slice(0, -1) --> [a] 此时b模块的父级模块是a模块
 let parent = path.slice(0, -1).reduce((root, current) => {
 return root[current]
 }, rootState)
 // 通过Vue.set设置数据双向绑定
 Vue.set(parent, path[path.length - 1], rootModule.state)
 }
 // 设置getter
 if (rootModule._raw.getters) {
 myforEach(rootModule._raw.getters, (getterName, getterFn) => {
 Object.defineProperty(store.getters, getterName, {
 get: () => {
 return getterFn(rootModule.state)
 }
 })
 })
 }
 // 在跟模块设置actions
 if (rootModule._raw.actions) {
 myforEach(rootModule._raw.actions, (actionName, actionsFn) => {
 // 因为同是在根模块设置,子模块也有能相同的key
 // 所有把所有的都放到一个数组里面
 // 就变成了例如 [change, change] , 第一个是跟模块的actions的change,第二个是a模块的actions的change
 let entry = store.actions[actionName] || (store.actions[actionName] = [])
 entry.push(() => {
 const commit = store.commit
 const state = rootModule.state
 actionsFn.call(store, {state, commit})
 })
 })
 }
 // 在跟模块设置mutations, 同理上actions
 if (rootModule._raw.mutations) {
 myforEach(rootModule._raw.mutations, (mutationName, mutationFn) => {
 let entry = store.mutations[mutationName] || (store.mutations[mutationName] = [])
 entry.push(() => {
 mutationFn.call(store, rootModule.state)
 })
 })
 }
 // 递归遍历子节点的设置
 myforEach(rootModule._children, (childName, module) => {
 installModules(store, rootState, path.concat(childName), module)
 })
}

const install = _Vue => {
 // 避免vuex重复安装
 if (Vue === _Vue) return
 Vue = _Vue
 Vue.mixin({
 // 通过mixins让每个组件实例化的时候都会执行下面的beforeCreate
 beforeCreate () {
 // 只有跟节点才有store配置
 if (this.$options && this.$options.store) {
 this.$store = this.$options.store
 } else if (this.$parent && this.$parent.$store) { // 子组件深度优先 父 --> 子---> 孙子
 this.$store = this.$parent.$store
 }
 }
 })
}

export default { install, Store }

看到代码以及注释,主要流程就是根据递归的方式,处理数据,然后根据传进来的配置,进行操作数据。

至此,我们把vuex的代码实现了一遍,在我们App.vue的代码里添加

<template>
 <div id="app">
 这里是store的state------->{{this.$store.state.count}} <br/>
 这里是store的getter------->{{this.$store.getters.newCount}} <br/>
 这里是store的state.a------->{{this.$store.state.a.count}} <br/>
 <button @click="change">点击触发dispach--> actions</button>
 <button @click="change1">点击触发commit---> mutations</button>
 </div>
</template>

最后查看结果。

源码地址:https://github.com/naihe138/write-vuex

Copyright © 2019- worldimage.cn 版权所有

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务