vue系列-vuex

Vuex官网:https://vuex.vuejs.org/zh/

基础

vuex是什么

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

Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。

vuex

状态自管理应用
  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。
单向数据流

data-flow

使用场景

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

核心内容

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的**状态 (state)**。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

store

store 就是一个数据仓库,为了更方便的管理仓库,我们把一个大的 store 拆成一些 modules ,整个 modules 是一个树型结构。每个 module 又分别定义了 state、getters、mutations、actions,通过递归遍历模块的方式完成了它们的初始化。

state

从 store 实例中读取状态最简单的方法就是在 计算属性 中返回某个状态:

如每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。

然而,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。

Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex))。

通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。

getters

从 state 中派生出一些状态,可以认为是 store 的计算属性。例如对列表进行过滤并计数:

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

注意,在 Vue computed 中使用 getter时, getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。

mutations

mutation 是同步进行 state 数据操作的。

更改 store 中的状态。

每个 mutation 都有一个字符串的事件类型(type)和一个回调函数(handler)

注意:

  • 最好提前在 store 中初始化好所有所需属性。
  • 当需要在对象上添加新属性时,应该建立起响应式关系:
    • 使用 Vue.set(obj, 'newProp', 123)
    • 以新对象替换老对象

actions

action 可以进行异步操作。action 提交的是 mutation。

内部会进行 Promise 化,最终调用结果返回是一个 promise 。

使用 store.dispatch 的方法进行派发 action。

dispatch (_type, _payload) {
    const {
        type,
        payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]

    // ...
    const result = entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
    // ...
}

promise.all 可以最大限度进行异步操作并发

在多个action中修改同一个state,因为很有可能每个action赋给state的新值都有所不同,并且不能保证最后一个有返回结果action是哪一个action,所以最后赋予state的值可能是错误的

modules

可以将 Vuex 分为多个模块甚至嵌套子模块,但是需要注意命名空间的使用。

API

mapState
mapGetters
mapActions
mapMutations
createNamespacedHelpers

通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

Vuex 提供这些 API 都是方便我们在对 store 做各种操作来完成各种能力,尤其是 mapXXX 的设计,让我们在使用 API 的时候更加方便,这也是我们今后在设计一些 JavaScript 库的时候,从 API 设计角度中应该学习的方向。

插件

Vuex 从设计上支持了插件,让我们很好地从外部追踪 store 内部地变化,Logger 插件在我们地开发阶段也提供了很好地指引作用。当然我们也可以自己去实现 Vuex 的插件,来帮助我们实现一些特定的需求。

源码

工具函数

深拷贝

https://datura35422.github.io/posts/57752/#Vuex%E6%BA%90%E7%A0%81

循环对象键值对
/**
 * forEach for object
 */
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}
判断数据类型

判断数据是否是对象应该还要考虑数组的情况 typeof [] === 'object'

判断数据是否是 promise 类型,其实就是判断 thenable 类型

export function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}

export function isPromise (val) {
  return val && typeof val.then === 'function'
}
高级函数

使用闭包的方式将函数和参数存储起来,延迟执行。

export function partial (fn, arg) {
  return function () {
    return fn(arg)
  }
}

面试题

基于Vue设计一个购物车(组件结构,vuex state 数据结构)

Vuex面试题汇总

vuex为什么不建议在action中修改state

更多阅读

https://github.com/zero2one3/Vuex-Analysis

学习 vuex 源码整体架构,打造属于自己的状态管理库 - 若川视野https://github.com/lxchuan12/vuex-analysis

https://ustbhuangyi.github.io/vue-analysis/v2/vuex/#vuex-%E6%A0%B8%E5%BF%83%E6%80%9D%E6%83%B3

Vuex 4.0 正式发布!新年,官方生态齐聚一堂。