博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
状态管理学习(二)Vuex、简单模拟Vuex
阅读量:242 次
发布时间:2019-02-28

本文共 12883 字,大约阅读时间需要 42 分钟。

Vuex 概念回顾

什么是 Vuex

  • Vuex 是专门为 Vue.js 设计的状态管理库(JS库)
  • Vuex 采用集中式的方式存储需要共享的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
    • 相比简单 store 模式 在状态过多时不宜管理
    • Vuex 提供了一种模块的机制,可以按模块划分不同功能的状态
  • Vuex 的作用是进行状态管理,解决复杂组件通信,数据共享
  • Vuex 集成到了Vue的官方调试工具 devtools extension 中,提供了 time-travel 时光旅行和历史回滚功能等功能

什么情况下使用 Vuex

  • 非必要的情况不要使用 Vuex
    • 如果项目不大,并且组件间状态共享不多的情况下,使用 Vuex 的益处并没有付出的时间多
    • 此时使用简单的 store 模式,或其他方式就可以满足需求
  • 建议大型的单页应用程序中使用 Vuex,可以更好的管理组件间共享的状态
    • 多个视图依赖同一状态
    • 来自不同试图的行为需要变更同一状态

官方文档:

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

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

Flux 架构就像眼镜:您自会知道什么时候需要它。

Vuex 核心概念

在这里插入图片描述

流程:

  • state - 管理的全局状态
  • Vue Components - 把状态绑定(渲染 Render)到组件(视图)展示给用户
  • 用户通过 Dispatch 分发 Action
    • 可以执行异步请求 Backend API
  • 请求完 Commit(提交)Mutation,改变状态的更改并记录

核心概念:

  • Store - 仓库
    • Store 是使用Vuex应用程序的核心
    • 每个应用仅有1个 Store
    • Store 是一个容器,包含应用中的大部分状态
    • 不能直接改变 Store 中的状态,要通过提交 Mutation 的方式改变状态
  • State - 状态
    • 状态保存在 Store 中,并且是响应式的
    • 因为 Store 是唯一的,所以状态也是唯一的,称为单一状态树
    • 但是所有的状态都保存在 State 中的话,会让程序难以维护,可以通 过Module (模块)解决该问题
  • Getter - 类似计算属性
    • 方便从一个属性派生其他的值
    • 内部可以对计算的结果进行缓存
    • 只有当依赖的状态发生改变的时候,才会进行计算
  • Mutation - 通过提交 Mutation 改变状态
    • Mutations 是同步的
    • 所有状态的变化必须要通过提交 Mutation 来完成,目的是:
      • 可以通过 Mutations 追踪到数据的变化
      • 阅读代码是,更容易分析应用内部的状态改变
      • 还可以记录每次状态的改变,实现高级调试功能,例如:time travel 和 历史回滚
  • Action - 和 Mutation 类似,也是用于改变状态
    • 不同的是 Action 可以进行异步的操作
    • 内部改变状态的时候,还是要提交 Mutation
  • Module - 模块
    • 由于使用单一状态树,应用的所有状态会集中到一个比较大的状态上来
    • 当应用变得复杂时,Store 对象就有可能变得相当臃肿
    • 为了解决以上问题,Vuex 允许将 Store 分割成模块
    • 每个模块拥有自己的 State Mutation Action Getter,甚至是嵌套的子模块

Vuex 基本结构

使用Vue CLI 创建项目的时候,如果选择了 Vuex,会自动生成 Vuex 代码的基本结构。

// src/store/index.jsimport Vue from 'vue'// 导入 Vueximport Vuex from 'vuex'// 注册插件Vue.use(Vuex)// 创建并导出 Vuex中的 Store 对象并导出export default new Vuex.Store({
state: {
}, mutations: {
}, actions: {
}, modules: {
} // getters})
// src/main.jsimport Vue from 'vue'import App from './App.vue'import router from './router'// 导入 store 对象import store from './store'Vue.config.productionTip = falsenew Vue({
router, // 创建 Vue 实例时传入 store 选项 // store 选项会被注入到 Vue 实例中:this.$store store, render: h => h(App)}).$mount('#app')

State

State 是单一状态树,用一个对象存储了全部的应用层级状态(所有的状态数据)。

并且 State 是响应式的。

在仓库(Store)中设置的状态都可以在组件中直接使用,获取数据时,直接从 State 中获取。

// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({
state: {
count: 0, msg: 'Hello Vuex' }, mutations: {
}, actions: {
}, modules: {
}})
// src/app.vue

Vuex 内部提供了 mapState 函数,自动生成状态对应的计算属性。

// src/app.vue
  • 使用 mapState 可以让视图中的代码更简洁。
  • 但是如果组件中已经有 count 或 msg 属性,再使用 mapState 就会造成命名冲突。

mapState 还可以接收对象作为参数,使用对象方式可以修改生成的计算属性的名称(解决命名冲突):

// src/app.vue

Getter

Vuex 中的 Getter 类似计算属性,可以对 state 中的数据进行处理再展示。

  • 接收 state 作为参数
  • 与计算属性一样,最终返回属性处理后的值
// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({
state: {
count: 0, msg: 'Hello Vuex' }, getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('') } }, mutations: {
}, actions: {
}, modules: {
}})
// src/app.vue

它同样有简化的函数:mapGetters,使用和 mapState 类似。

  • 它用于把 Vuex 中的 getter 映射到组件中的计算属性。
  • 同样接收数组和对象,使用和 mapState 一样
  • 同样返回一个数组
// src/app.vue

Mutation

Vuex 约定,更改 store 中状态的唯一方法是提交 Mutation,目的是方便在 devtools 中调试。

  • Mutation 必须是同步执行的,这样可以保证能够在 Mutation 中收集到所有的状态修改。

  • 不要在 Mutation 中执行异步操作修改 State,否则调试工具无法正常的观测到数据的变化。

  • 如果想要执行异步的操作,需要使用 Action。

Vuex 中的mutation非常类似于事件:每个mutation都有一个字符串的 事件类型(type) 和一个 回调函数(handler)

这个回调函数就是我们实际进行状态更改的地方,它接收两个参数:

  • state - 状态
  • payload - 载荷,是调用 Mutation 时传递的额外参数
    • 多个参数可以包装到一个对象中传递

调用 Store 中的 Mutation 类似于事件,它需要通过 $store.commit 提交,它接收:

  • Muataion 的名字作为第一个参数,相当于事件的名称
  • 还可以接收第二个参数用于传递额外的参数,即 Mutation 的载荷 payload

使用 mutation 改变状态的好处是:集中的一个位置对状态修改,不管在什么地方修改,都可以追踪到状态的修改。可以实现高级的 time travel 调试功能。

可以通过 mapMutations 把 Mutation 映射到组件的 methods 中,mapMutations 返回的方法封装了 commit 的调用。

// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({
state: {
count: 0, msg: 'Hello Vuex' }, getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('') } }, mutations: {
increate (state, payload) {
state.count += payload } }, actions: {
}, modules: {
}})
// src/app.vue

Vue devtools 查看时光旅行和历史回滚

打开安装好的 chrome 浏览器插件: vue devtools,查看 Vuex:

在这里插入图片描述

  • 左侧是 mutations 提交记录
    • Base State 初始 state
  • 右侧是 本次提交的 mutation 、state、getters

mutation 每次提交记录中提供3个操作,目的是为了方便调试,从右向左依次是:

  • Time Travel to This State - 时光旅行
    • 点击会跳转到本次 mutation 提交的时候
  • Revert This Mutation - 历史回滚
    • 回滚到本次提交之前,点击后,本次和之后的提交都被清空
  • Commit This Mutation - 把本次提交,作为最后一次提交
    • 点击后,会把本次记录重置为最后一次提交的记录,并向上重置,直到 Base State,清空没被重置的记录

Action

在Action中可以执行异步操作,在异步操作之后,如果需要修改状态,可以通过提交 Mutation 来修改 State(所有的状态更改都要通过 Mutation)。

Action 接收两个参数:

  • context - 上下文,包含 state、getters、commit、dispatch 等属性
  • payload - 额外的参数

调用 Store 中的 Action 和 通过 commit 调用 Mutation 一样,它需要通过 $store.dispatch 提交,它接收:

  • Action 的名字作为第一个参数
  • 第二个参数用于传递额外的参数,即 payload

可以通过 mapActions 把 Action 映射到组件的 methods 中,mapActions 返回的方法封装了 dispatch的调用。

// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({
state: {
count: 0, msg: 'Hello Vuex' }, getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('') } }, mutations: {
increate (state, payload) {
state.count += payload } }, actions: {
increateAsync (context, payload) {
setTimeout(() => {
context.commit('increate', payload) }, 1000) } }, modules: {
}})
// src/app.vue

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。

当应用变得非常复杂时,store 对象就有可能变得非常臃肿。

Vuex 可以把 单一状态树拆(store )分成多个模块(module)。

每个模块都可以拥有自己的 state、mutations、actions、getters,甚至嵌套子模块。

定义模块

  • 模块中只是定义并导出了相关的成员。
// src/store/products.jsconst state = {
products: [ {
id: 1, title: 'iPhone 11', price: 8000 }, {
id: 1, title: 'iPhone 12', price: 10000 } ]}const getters = {
productCount (state) {
return state.products.length }}const mutations = {
setProducts (state, payload) {
state.products = payload }}const actions = {
}export default {
state, getters, mutations, actions}
// src/store/cart.jsconst state = {
}const getters = {
}const mutations = {
}const actions = {
}export default {
state, getters, mutations, actions}

注册模块

  • 导入模块
  • 在 modules 中注册模块
// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'// 导入模块import products from './products'import cart from './cart'Vue.use(Vuex)export default new Vuex.Store({
state: {
count: 0, msg: 'Hello Vuex' }, getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('') } }, mutations: {
increate (state, payload) {
state.count += payload } }, actions: {
increateAsync (context, payload) {
setTimeout(() => {
context.commit('increate', payload) }, 1000) } }, // 注册模块 modules: {
products, cart }})

注册完(可以打印 store 看看):

  • 会把模块中的状态挂载到 $store.state 中
    • 可以通过 $store.state.[模块名].[模块中state的属性名] 方法
  • 把模块的 mutations、actions 记录到 $store 的内部属性 _mutations_actions
    • _mutations_actions 以对象形式存放 Store 中所有的 mutations 和 actions
      • key 是 mutations / actions 的名称
      • value 是所有 mutations / actions 同名的方法组成的数组
    • 可以通过 $store.commit 直接提交模块中的 mutation
    • 可以通过 $store.dispatch直接提交模块中的 action
  • 把模块的 getters 记录到 $store 的 getters 中
    • 它没有根据模块分类(类似state),也没有收集所有方法(类似 mutations / actions),它是平铺存储,注意避免命名冲突
// src/app.vue

当前所演示的模块中的 mutations、actions、getters 都直接存储在 Store 中。

如果想要模块有更好的封装度和复用性,可以给模块开启命名空间。

将来在视图中使用模块中的成员的时候,看起来更清晰一些。

模块命名空间

推荐使用命名空间的用法。

在模块导出的对象中,通过namespacing 开启命名空间。

// src/store/cart.jsconst state = {
}const getters = {
}const mutations = {
}const actions = {
}export default {
// 开启命名空间 namespaced: true, state, getters, mutations, actions}// src/store/products.js 一样

打印 Store 对比,发现 mutations、actions、getters 中模块的成员名都添加了模块名/作为前缀:

在这里插入图片描述

在视图中通过 mapXxxx 方法映射模块的成员:

  • 第一个参数是模块命名空间的名字,也就是在 Store 的 modules 中定义的模块的名字。
  • 第二个参数依然是数组或对象
// src/app.vue

Vuex 严格模式

Vuex 约定所有状态的变更都应该通过提交 Mutation。

但是语法上,可以直接访问和修改 $store.state 中的属性。

这破坏了Vuex的约定。

如果在组件中直接修改 state,devtools 无法跟踪到状态的修改。

开启严格模式之后,在组件中直接修改 state ,虽然还是会生效,但是会抛出错误:

[vuex] do not mutate vuex store state outside mutation handlers.# 不要在 mutation 之外修改 vuex 中的状态

在创建 Store 的时候通过 strict 选项开启:

// 创建并导出 Vuex中的 Store 对象并导出export default new Vuex.Store({
strict: true, // ...})
{
{msg}}

注意:不要在生产环境开启严格模式。

严格模式会深度检查状态树,检查不合规的状态改变,会影响性能

可以在开发环境启用严格模式,在生产环境关闭严格模式。

export default new Vuex.Store({
// 打包时根据环境变量进行处理 // npm run serve 时 NODE_ENV = development // npm run build 时 NODE_ENV = production strict: process.env.NODE_ENV !== 'production')}

Vuex 的插件

以购物车为例,用户的购物车商品信息是记录在本地 localStorage 中,以保证刷新页面,还能保留记录。

这样就需要在每个修改(增、删等)购物车商品信息的 mutation 中更新 localStorage 中的数据。

可以使用 Vuex 的插件简化这个操作。

  • Vuex 的插件就是要给函数
  • 这个函数接收一个 store 的参数
  • 这个函数中可以注册(store.subscribe)一个函数,可以在每个 mutation 结束后执行
    • subscribe 用于订阅 mutation
    • 它注册的回调函数,会在每个 mutation 完成之后调用
  • 通过 store 的 plugins 选项注册
  • 插件要在创建 store 之前创建

例如:

const myPlugin = store => {
// 当 store 初始化后调用 store.subscribe((mutation, state) => {
// subscribe 用于订阅 mutation // 注册的回调函数,会在每个 mutation 完成之后调用 // mutaion -> {type, payload} // type:'命名空间/mutaion的名字' // payload:mutation接收的参数 // state:store的state,不是模块的 })}

模拟简单的 Vuex

Vuex 基本结构

Vuex 模块包含:

  • Store 类 - 用于实例化 Store
  • install 函数 - 用于 Vue.use 注册 Vuex 插件
// src/myvuex/index.jslet _Vue = nullclass Store {
}function install (Vue) {
_Vue = Vue}export default {
Store, install}

install

install 的工作是把创建Vue实例时传入的 store 对象,注入到Vue原型上的$store。

在所有组件中都可以通过 this.$store 获取到Vuex中的仓库,从而共享状态。

let _Vue = nullfunction install (Vue) {
_Vue = Vue // 在install中获取不到Vue的实例 // 所以需要混入 beforeCreate 来获取Vue实例 // 从而拿到选项中的 store 对象 _Vue.mixin({
beforeCreate () {
// 判断$options中是否有store选项 // 组件实例没有store,不需要处理 if (this.$options.store) {
_Vue.prototype.$store = this.$options.store } } })}

Store 类

  • 构造函数 - 接收一个选项对象
  • 属性:
    • state - 响应式的
    • getters
    • mutations
    • actions
  • 方法:
    • commit - 提交 mutation
    • dispatch - 分发 action

初始化:

  • this.state - 初始化为选项中传入的 state 进行响应式处理后的对象
  • this.getters
    • 选项传入的 getters 是一个对象,对象属性的值都是一个方法
    • 这些方法都接收一个 state 参数,并最终都有返回值
    • 一般情况下就是对状态作简单的处理,把结果返回
    • 这些方法的作用都是当访问getters中的成员的时候,去获取值
    • 可以把这些方法,通过 Object.defineProperty 转换成 this.getters 对象中的 get 访问器
    • 并将 state 传入
  • this._mutations && this._actions - 直接存储 选项传入的对象
    • 它们是内部属性,在commit 或 dispatch 方法中获取
    • 在它们的名称前加上下划线前缀表示私有,不希望外部访问
  • commit
    • 接收两个参数
      • type - this._mutations中方法的名字
      • payload - 调用方法时传入的参数
    • 内部调用 mutation 的方法,并传入:
      • state - 当前实例的state
      • payload
  • dispatch
    • 参数与 commit 类似
    • 内部调用 action 的方法,并传入:
      • context - 上下文,也就是当前实例
      • payload - 调用方法时传入的参数
class Store {
constructor(options) {
const {
state = {
}, getters = {
}, mutations = {
}, actions = {
} } = options // 对 state 进行响应式处理 this.state = _Vue.observable(state) // this.getters 不需要原型 this.getters = Object.create(null) // 将 getters 中的方法转换成 this.getters 对象的get 范文其 Object.keys(getters).forEach(key => {
Object.defineProperty(this.getters, key, {
// 获取 getters 中方法的执行结果 get: () => getters[key](state) }) }) // mutations和actions 在commit 或 dispatch 方法中获取 // 所以它们应该是内部私有成员,下划线前缀标识私有,不希望外部访问 this._mutations = mutations this._actions = actions } commit (type, payload) {
this._mutations[type](this.state, payload) } dispatch (type, payload) {
this._actions[type](this, payload) }}

到此Vuex模拟完成,替换vuex的模块地址为 myvuex的地址,可以测试效果。

转载地址:http://dfzp.baihongyu.com/

你可能感兴趣的文章