#每天都是崭新的一天#再累也要照顾好自己

浅析Vuex 的设计思想

一、前言

在聊之前,大家要始终记得一句话:一切前端概念,都是纸老虎。

不管是Vue,还是 React,都需要管理状态(state),比如组件之间都有共享状态的需要。什么是共享状态?比如一个组件需要使用另一个组件的状态,或者一个组件需要改变另一个组件的状态,都是共享状态。

如果不对状态进行有效的管理,状态在什么时候,由于什么原因,如何变化就会不受控制,就很难跟踪和测试了。如果没有经历过这方面的困扰,可以简单理解为会搞得很乱就对了

在软件开发里,有些通用的思想,比如隔离变化约定优于配置等,隔离变化就是说做好抽象,把一些容易变化的地方找到共性,隔离出来,不要去影响其他的代码。约定优于配置就是很多东西我们不一定要写一大堆的配置,比如我们几个人约定,view 文件夹里只能放视图,不能放过滤器,过滤器必须放到 filter 文件夹里,那这就是一种约定,约定好之后,我们就不用写一大堆配置文件了,我们要找所有的视图,直接从 view 文件夹里找就行。

根据这些思想,对于状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测


二、什么是vuex

官方解释: Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

大白话:对数据(data)统一的管理,如果涉及到了数据的处理,到vuex里面进出吧!就像是超市对商品的统一管理一样。

设计思想:Vuex 全局维护着一个对象,使用到了单例设计模式。在这个全局对象中,所有属性都是响应式的,任意属性进行了改变,都会造成使用到该属性的组件进行更新。并且只能通过 commit 的方式改变状态,实现了单向数据流模式。


三、源码解读

store 对象中有一个属性叫 state。state 包含了全部的应用层级状态。应用中的各个组件若使用了 state,则会保持与同步最新的状态。state 就好比是 vue 中的 data,但它是整个应用的 data。其实在想vuex是不是和vue的实现双向数据绑定一样,做了 object.defineproperty() 的处理。

我们从 store 对象被实例化的代码中开始。

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0
  }
});

new Vue({
  el: '#app',
  store,
  // ...
});

store 对象是通过 new Vuex.Store 被实例化出来的,打开 src/index.js 文件,最下面的代码中可以看到 vuex 暴露出的 Store 这个 API:

export default {
  Store,
  install,
  mapState,
  mapMutations,
  mapGetters,
  mapActions
}


3.1 store

找到 Store 这一个类,挺长的一大段代码。老办法,全部方法折叠不看,只看构造函数 constructor。过滤掉与 state 无关的代码,最后可以把代码简化成这样:

constructor (options = {}) {
  const {
    state = {}
  } = options

  // init root module.
  // this also recursively registers all sub-modules
  // and collects all module getters inside this._wrappedGetters
  installModule(this, state, [], options)

  // initialize the store vm, which is responsible for the reactivity
  // (also registers _wrappedGetters as computed properties)
  resetStoreVM(this, state)
}

瞬间代码少了好多,接下来只需要关注installModuleresetStoreVM两个方法的实现即可。紧接着,再透露一个好消息,installModule看了一眼,是关于 module 的初始化,等以后研究module再看也不迟,所以只需要研究resetStoreVM即可。


3.2 resetStoreVM

定位到 resetStoreVM 方法,再次过滤,其中 Vue.config.silent 去 vue 的官网上搜了一下,是取消 vue 所有的日志与警告的功能,所以也过滤掉,剩下代码如下:

function resetStoreVM (store, state) {
  // use a Vue instance to store the state tree
  store._vm = new Vue({
    data: { state }
  })
}

resetStoreVM 函数做的事情就是给 store 添加一个 _vm 属性,并将 state 作为一个 vue 对象的 data,最后将这个 vue 对象赋值给 _vm。所以到这里我们知道了 store 类的构造函数为其添加了一个 _vm 属性。


3.3 set和get

构造函数解析完毕,接下来得继续找跟 state 相关的方法,于是找到了 set 和 get 方法:

get state () {
  return this._vm.state
}

set state (v) {
  assert(false, `Use store.replaceState() to explicit replace store state.`)
}

set 方法没什么好说的,意思就是 store 不给你直接修改 state(但其实是可以修改 state 对象的属性,先不管那么多了)。

get方法返回了刚刚构造函数添加的 _vm 属性的一个 data(state)。阅读到这里,这下我们应该知道为什么组件 a 修改了 state.count,组件 b 也会跟着变了吧。因为 state 就是一个新的 vue 对象里 data 的一个属性啊。


 四、常见的API

4.1 commit 解析

如果需要改变状态的话,一般都会使用 commit 去操作,接下来让我们来看看 commit 是如何实现状态的改变的。

commit(_type, _payload, _options) {
  // 检查传入的参数
  const { type, payload, options } = unifyObjectStyle(
    _type,
    _payload,
    _options
  )

  const mutation = { type, payload }
  // 找到对应的 mutation 函数
  const entry = this._mutations[type]
  // 判断是否找到
  if (!entry) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown mutation type: ${type}`)
    }
    return
  }
  // _withCommit 函数将 _committing
  // 设置为 TRUE,保证在 strict 模式下
  // 只能 commit 改变状态
  this._withCommit(() => {
    entry.forEach(function commitIterator(handler) {
      // entry.push(function wrappedMutationHandler(payload) {
      //   handler.call(store, local.state, payload)
      // })
      // handle 就是 wrappedMutationHandler 函数
      // wrappedMutationHandler 内部就是调用
      // 对于的 mutation 函数
      handler(payload)
    })
  })
  // 执行订阅函数
  this._subscribers.forEach(sub => sub(mutation, this.state))
}


4.2 dispatch解析

如果需要异步改变状态,就需要通过 dispatch 的方式去实现。在 dispatch 调用的 commit 函数都是重写过的,会找到模块内的 mutation 函数。

dispatch(_type, _payload) {
  // 检查传入的参数
  const { type, payload } = unifyObjectStyle(_type, _payload)

  const action = { type, payload }
  // 找到对于的 action 函数
  const entry = this._actions[type]
  // 判断是否找到
  if (!entry) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown action type: ${type}`)
    }
    return
  }
  // 触发订阅函数
  this._actionSubscribers.forEach(sub => sub(action, this.state))

  // 在注册 action 的时候,会将函数返回值
  // 处理成 promise,当 promise 全部
  // resolve 后,就会执行 Promise.all
  // 里的函数
  return entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
}


4.3 各种语法糖

在组件中,如果想正常使用 Vuex 的功能,经常需要这样调用this.$store.state.xxx的方式,引来了很多的不便。为此,Vuex 引入了语法糖的功能,让我们可以通过简单的方式来实现上述的功能。以下以mapState为例,其他的几个 map 都是差不多的原理,就不一一解析了。

function normalizeNamespace(fn) {
  return (namespace, map) => {
    // 函数作用很简单
    // 根据参数生成 namespace
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
    }
    return fn(namespace, map)
  }
}
// 执行 mapState 就是执行
// normalizeNamespace 返回的函数
export const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  // normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
  // normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
  // function normalizeMap(map) {
  //   return Array.isArray(map)
  //     ? map.map(key => ({ key, val: key }))
  //     : Object.keys(map).map(key => ({ key, val: map[key] }))
  // }
  // states 参数可以参入数组或者对象类型
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = function mappedState() {
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        // 获得对应的模块
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      // 返回 State
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})


五、总结

以上是 Vuex 的源码解析,虽然 Vuex 的整体代码并不多,但是却是个值得阅读的项目。了解了 store 的 state,知道它是通过 new 一个新的 vue 对象 _vm 来监听的,而这个 _vm 又是绑在 store 上的。所以通过这一系列的关系,最后我们能在各个组件中使用到被监听的 this.$store.state


文章转载:原创
感谢你的阅读,本文由 sau交流学习社区 版权所有。
如若转载,请注明出处:sau交流学习社区-power by saucxs(程新松)(/page/645.html)
交流咨询
    官方QQ群
    群号663940201,欢迎加入!
    sau交流学习社区交流群

微信群
欢迎加入微信群
微信公众号
欢迎关注微信公众号
微信群
saucxs聊天机器人
saucxs
hi ,欢迎来到sau交流学习社区,欢迎与我聊天,问我问题哦!
您正在使用的浏览器是,不在支持范围内!
为了您的正常使用与展示,推荐使用Chrome浏览器68以上版本
支持浏览器:
火狐浏览器最新版
Safari浏览器最新版
Edge浏览器最新版
IE浏览器10,11