Vue-day13 vuex


vuex概念

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

为什么要vuex

如果已经学过React 可以跳过这里,因为这个跟redux基本一样的

如果有代码基础,Vuex也可以理解为java中的一个map,这个map是static(静态资源)的,每次获取map的值,都需要调用java的api,比如map.get(key)获取对应的值,也可以放入数据map.put(data),而且这个数据是所有类都可以调用的,只需要导入这个map就能使用里面的共享数据。

 当访问数据对象时,一个 Vue 实例只是简单的代理访问。所以,如果有一处需要被多个实例间共享的状态,可以简单地通过维护一份数据来实现共享

const sourceOfTruth = {}
const vmA = new Vue({
  data: sourceOfTruth
})
const vmB = new Vue({
  data: sourceOfTruth
})

  现在当 sourceOfTruth 发生变化,vmAvmB 都将自动的更新引用它们的视图。子组件们的每个实例也会通过 this.$root.$data 去访问。现在有了唯一的实际来源,但是,调试将会变为噩梦。任何时间,应用中的任何部分,在任何数据改变后,都不会留下变更过的记录。

  为了解决这个问题,采用一个简单的 store 模式

var store = {
  debug: true,
  state: {
    message: 'Hello!'
  },
  setMessageAction (newValue) {
    if (this.debug) console.log('setMessageAction triggered with', newValue)
    this.state.message = newValue
  },
  clearMessageAction () {
    if (this.debug) console.log('clearMessageAction triggered')
    this.state.message = ''
  }
}

  所有 store 中 state 的改变,都放置在 store 自身的 action 中去管理。这种集中式状态管理能够被更容易地理解哪种类型的 mutation 将会发生,以及它们是如何被触发。当错误出现时,现在也会有一个 log 记录 bug 之前发生了什么

  此外,每个实例/组件仍然可以拥有和管理自己的私有状态:

var vmA = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})
var vmB = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})

组件不允许直接修改属于 store 实例的 state,而应执行 action 来分发 (dispatch) 事件通知 store 去改变,最终达成了 Flux 架构。这样约定的好处是,能够记录所有 store 中发生的 state 改变,同时实现能做到记录变更 (mutation)、保存状态快照、历史回滚/时光旅行的先进的调试工具

所以这里就可以基本理解为什么要store,为什么要Vuex了吧

什么状态需要Vuex去管理?

  • 比如用户的登录的状态(token)、用户的信息(头像、名称、地理位置信息)等等
  • 比如商品的收藏,购物车的商品等等
  • 这些状态应该是响应式的,用户昵称、头像修改了需要响应

Vue的单向数据流

image-20210214142126665

示意图说明:

  • State:驱动应用的数据源(单向数据流)
  • View:以声明方式将 state 映射到视图(静态显示出来的数据源)
  • Actions:处理用户在view上面操作而导致的状态变化(数据源变化追踪)

vuex与全局变量的区别

  • 响应式:vuex的状态存储是响应式的,当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会得到高效更新
  • 不能直接改变store:不能直接改变store的变化,改变store中状态的唯一途径是commit mutation,方便于跟踪每一个状态的变化

这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux、和 The Elm Architecture。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新

vuex核心流程

示意图说明:

  1. Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应
  2. Dispatch:操作行为触发方法,是唯一能执行action的方法
  3. Actions:操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发
  4. Commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法
  5. Mutations:状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等
  6. State:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新
  7. Getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象

安装

npm install vuex --save

项目结构

Vuex 并不限制代码结构。但是,它规定了一些需要遵守的规则:

  1、应用层级的状态应该集中到单个 store 对象中

  2、提交 mutation 是更改状态的唯一方法,并且这个过程是同步的

  3、异步逻辑都应该封装到 action 里面

  只要遵守以上规则,可以随意组织代码。如果store文件太大,只需将 action、mutation 和 getter 分割到单独的文件

  对于大型应用,希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块

实例操作

看下面之前,先想想计数器要是使用Vue怎么实现

然后看下面的基本语法,实现一个Vue的计数器

1.state-状态对象的获取方法(组件内)

在组件的template中直接使用

<h2>{{ $store.state.count }}</h2>

由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态

1、在计算属性computed中直接赋值

computed: {
    count() {
        // this指的是main.js中的vue实例对象
        return this.$store.state.count;
    }
}

2、通过mapState的对象来赋值

mapState 函数返回的是一个对象

import { mapState } from 'vuex'
computed: mapState({
    // es5写法
    count: function (state) {
         return state.count;
     },
    // es6写法
    count: state => state.count,
    // 想访问局部状态,就必须借助于一个普通函数,函数中使用 `this` 获取局部状态
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
})

通过mapState的数组来赋值- 常用

import { mapState } from 'vuex'
// 映射 this.count 为 store.state.count
computed: mapState(['count'])

通过mapState的JSON来赋值- 常用

import { mapState } from 'vuex'
computed: mapState({
    count: 'count'
})

3.store中的Getter函数 - store从state取数据

设置store中Getter,然后组件中直接取值

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})
computed: {
...mapGetters([
  'doneTodos''other'
])
}
//或者
computed: {
  doneCount() {
    return this.$store.getters['doneTodosCount']
  }
}

注意!!Getter 也可以接受其他 store中getter 作为第二个参数

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
store.getters.doneTodosCount // -> 1

2.mutations-getters-actions异步

mutations(修改状态)

无参数的基本版本

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})
store.commit('increment')//调用

提交载荷(Payload) - 有参数

mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10)

 在大多数情况下,载荷应该是一个对象

提交载荷(Payload) 带type

 提交 mutation 的另一种方式是直接使用包含 type 属性的对象

store.commit({
  type: 'increment',
  amount: 10
})

  当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

Mutation必须是同步函数

actions和mutations功能基本一样,不同点是,actions是异步的改变state状态,而mutations是同步改变状态。不过实际项目中一般都是通过actions改变mutations中的值。

在组件中提交Mutation - mapMutations

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

严格模式

严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有

的状态变更都能被调试工具跟踪到。开启严格模式 strict: true

3.action

基本特点

  Action类似于mutation,不同之处在于:

  1、Action 提交的是 mutation,而不是直接变更状态

  2、Action 可以包含任意异步操作

基本的action写法

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}
store.dispatch('increment')

乍一眼看上去感觉多此一举,直接分发 mutation 岂不更方便?实际上并非如此,mutation必须同步执行这个限制,而Action 就不受约束,可以在 action 内部执行异步操作

//实际上还是不一样哦
actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

载荷方式和对象方式进行分发

// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

来看一个更加实际的购物车示例,涉及到调用异步 API 和分发多重 mutation

actions: {
  checkout ({ commit, state }, products) {
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求,然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

在组件中分发Action - mapActions

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

//或者
import { mapActions } from 'vuex'
export default {
  methods: {
    increment(...args) {
      return this.$store.dispatch.apply(this.$store, ['increment'].concat(args))
    }
    add(...args) {
      return this.$store.dispatch.apply(this.$store, ['increment'].concat(args))
    }
  }
} 

组合多个Action

Action 通常是异步的,那么多个action的时候怎么样才能保证action已经执行了呢

首先,需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

现在可以

store.dispatch('actionA').then(() => {
  // ...
})

在另外一个 action 中也可以:

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

最后,如果利用 async / await 这个 JavaScript 新特性,可以像这样组合 action

// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行

4.组件中执行命令

this.$store.dispatch('setWindowWidth', window.innerWidth);

5.module-模块组

基本结构

// 模块A
const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

// 模块B
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

// 组装
const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

// 取值
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

模块的局部状态

​ 对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

这里的练习在github

6.命名空间&测试Mutation

这里我没做练习弄明白 嘿嘿 所以想了解请看命名空间

Vuex的API

辅助函数

mapState

mapState(namespace?: string, map: Array<string> | Object): Object

  为组件创建计算属性以返回 Vuex store 中的状态。第一个参数是可选的,可以是一个命名空间字符串

mapGetters

mapGetters(namespace?: string, map: Array<string> | Object): Object

  为组件创建计算属性以返回 getter 的返回值。第一个参数是可选的,可以是一个命名空间字符串

mapActions

mapActions(namespace?: string, map: Array<string> | Object): Object

  创建组件方法分发 action。第一个参数是可选的,可以是一个命名空间字符串

mapMutations

mapMutations(namespace?: string, map: Array<string> | Object): Object

  创建组件方法提交 mutation。第一个参数是可选的,可以是一个命名空间字符串

createNamespacedHelpers

createNamespacedHelpers(namespace: string): Object

  创建基于命名空间的组件绑定辅助函数。其返回一个包含 mapStatemapGettersmapActionsmapMutations 的对象。它们都已经绑定在了给定的命名空间上

实例方法

commit(type: string, payload?: any, options?: Object) | commit(mutation: Object, options?: Object)

  提交 mutation。options 里可以有 root: true,它允许在命名空间模块里提交根的 mutation

dispatch(type: string, payload?: any, options?: Object) | dispatch(action: Object, options?: Object)

  分发 action。options 里可以有 root: true,它允许在命名空间模块里分发根的 action。返回一个解析所有被触发的 action 处理器的 Promise

replaceState(state: Object)

  替换 store 的根状态,仅用状态合并或时光旅行调试

watch(getter: Function, cb: Function, options?: Object)

  响应式地监测一个 getter 方法的返回值,当值改变时调用回调函数。getter 接收 store 的状态作为唯一参数。接收一个可选的对象参数表示 Vue 的 vm.$watch 方法的参数。

  要停止监测,直接调用返回的处理函数

subscribe(handler: Function)

  注册监听 store 的 mutation。handler 会在每个 mutation 完成后调用,接收 mutation 和经过 mutation 后的状态作为参数

store.subscribe((mutation, state) => {
  console.log(mutation.type)
  console.log(mutation.payload)
})

  通常用于插件

registerModule(path: string | Array<string>, module: Module)

  注册一个动态模块

unregisterModule(path: string | Array<string>)

  卸载一个动态模块

hotUpdate(newOptions: Object)

  热替换新的 action 和 mutation

Reference

vuex官方

vuex核心内容及重点细节总结

vuex介绍


Author: Savannah
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Savannah !
  TOC