Skip to content

Latest commit

 

History

History
446 lines (324 loc) · 12.5 KB

Vuex.md

File metadata and controls

446 lines (324 loc) · 12.5 KB

Vuex

官方介绍:

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

简单的说,它是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据共享。

vuex

注意:本文为 Vue 2.x 写法,Vue 3 详细的语法变更请查阅 Vuex 4.x 文档。此外,Vue 3.x 的下一代 Vuex 开发重心已转移到 pinia,它支持 Vue 2.x 和 Vue 3.x 版本。

Vuex Store 有 3 个主要概念:statemutationsactions,另外还有 gettersmodules,我们会在下文中一一讲解。

安装

首先,安装 vuex:

$ npm i vuex
# or
$ yarn add vuex

创建 store/index.js 文件:

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

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {},
  mutations: {},
  actions: {}
  // ...
})

export default store

在主文件 main.js 导入,并在 vue 实例中注入已经创建好的 store

import store from './store'

const app = new Vue({
  store,
  ...App
})

app.$mount()

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

State

State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 的 state 中进行储存。

state: {
  count: 1,
  msg: 'hello world!'
}
  • 获取方式一:使用 store.state 获取。
this.$store.state.count // 1
  • 获取方式二:使用 mapState 函数,将全局数据映射为当前组件的 computed 计算属性使用。
import { mapState } from 'vuex'

...mapState(['count', 'msg'])

Mutations

Mutations 用于变更 Store 中的状态,且 mutations 必须是同步的

Mutations 是改变存储中状态数据的唯一方法,通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监控所有数据的变化。

mutations: {
    // 定义事件处理函数
  increment(state) {
    // 变更状态
    state.count++
  },
  // 可以在触发 mutations 时传递参数:
  incrementN(state, step) {
    state.count += step
  },
  decrementN(state, step) {
    state.count += step
  }
}

调用方式一:使用 store.commit 方法。

// 调用 commit 函数
this.$store.commit('increment')
// 触发 mutations 时携带参数
this.$store.commit('incrementN', 3)

调用方式二:使用 mapMutations 函数,映射为当前组件的 methods 函数。

import { mapMutations } from 'vuex'

methods: {
  ...mapMutations(['increment', 'incrementN'])
}

Actions

Actions 的存在是因为 mutations 必须是同步的,而 actions 可以是异步的。

Actions 将允许我们异步更新状态,但将使用现有的 mutations。如果你需要按照特定的顺序同时执行几个不同的 mutations,这会非常有帮助。

actions: {
  asyncDecrementN({ commit }, asyncNum) {
    await new Promise((resolve) => setTimeout(resolve, asyncNum.duration))
    commit('decrementN', asyncNum.by)
  }
}
  • 获取方式一:使用 store.dispatch 获取异步方法。
// 触发 actions 异步任务时携带参数
methods: {
  asyncDecrementN() {
    this.$store.dispatch('asyncDecrementN', {
      by: -3,
      duration: 1000,
    })
  }
}
  • 获取方式二:使用 mapActions 函数,映射为当前组件的 methods 函数,内部会将 actions 处理为 Vue 实例方法。
import { mapActions } from 'vuex'

methods: {
  ...mapActions(['asyncDecrementN'])
}

需要注意的是,Vuex 不会为你处理异步操作中的错误。如果异步操作抛出错误,你将得到未处理的 Promise reject,除非你使用 .catch()async/await 显式处理该错误。

actions: {
  asyncDecrementN({ commit }, asyncNum) {
    await new Promise((resolve) => setTimeout(resolve, asyncNum.duration))
    throw new Error('Oops')
  }
}

const err = await this.$store
  .dispatch('asyncDecrementN', {
    by: -3,
    duration: 1000
  })
  .catch((err) => err)

err.message // "Oops"

关于 actions,还有一个非常有用的 API:subscribeAction API。你可以使用它注册一个回调,它会在每次进行 dispatch 操作时调用该回调。

store.subscribeAction((action, state) => {
  console.log(action) // { type: 'asyncDecrementN', payload: { by: -3, duration: 1000 } }
})

subscribeAction() API 是许多 Vuex plugins 的基础,因此使用 actions 可以让你更好地利用 Vue 社区的插件。

Getters

Getters 可以对 store 中已有的数据加工处理之后形成新的数据,类似 Vue 的计算属性。

// 展示数据,而不是更改状态
getters: {
  newCount: (state) => state.count * 3
}
  • 获取方式一:通过暴露的 store.getters 对象获取属性。
this.$store.getters.newCount
  • 获取方式二:使用 mapGetters 函数,将全局数据映射为当前组件的计算属性使用。
import { mapGetters } from 'vuex'

computed: {
  ...mapGetters(['newCount'])
}

getters 会对 store 状态的变化做出响应,就像计算属性一样。因此,如果你更新 getters 所依赖的状态属性之一,则 getters 值也会更新:

mutations: {
  changeCount(state, val) {
    state.count = val
  }
}

this.$store.commit('changeCount', 2)
this.$store.getters.newCount // 6

Modules

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

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationsactionsgetters

// module/moduleA.js
const moduleA = {
  state: () => ({
    name: '李四'
  }),
  mutations: {},
  actions: {},
  getters: {}
}

export default moduleA

// module/moduleB.js
const moduleB = {
  state: () => ({
    name: '老六'
  }),
  mutations: {},
  actions: {}
}

export default moduleB

// store/index.js
import moduleA from './module/moduleA'
import moduleB from './module/moduleB'
const store = new Vuex.Store({
  modules: {
    moduleA,
    moduleB
  }
})

store.state.moduleA // -> moduleA 的状态
store.state.moduleB // -> moduleB 的状态

详细信息请看 modules

其他

热重载

Vuex 支持在开发过程中热重载 mutationsmodulesactionsgetters

项目结构

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

  • 应用层级的状态应该集中到单个 store 对象中。
  • 提交 mutations 是更改状态的唯一方法,并且这个过程是同步的。
  • 异步逻辑都应该封装到 actions 里面。

如果你的 store 文件太大,只需将 actionsmutationsgetters 分割到单独的文件。对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

└── store
  ├── index.js          # 我们组装模块并导出 store 的地方
  ├── actions.js        # 根级别的 action
  ├── mutations.js      # 根级别的 mutation
  └── modules
    ├── cart.js       # 购物车模块
    └── products.js   # 产品模块

插件

Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutations 的钩子。Vuex plugins 就是一个函数,它接收 store 作为唯一参数:

const myPlugin = (store) => {
  // 当 store 初始化后调用
  store.subscribe((mutation, state) => {
    // 每次进行 commit 操作时调用该回调,mutation 的格式为 { type, payload }
  })
}

然后像这样使用:

const store = new Vuex.Store({
  // ...
  plugins: [myPlugin]
})

表单处理

在 Vuex 严格模式下,状态变更需要在 mutations 函数下执行。在你不了解这个的情况下,使用 v-model 时,可能会发生一些错误。

<input v-model="obj.message" />

假设这里的 obj 是在计算属性中返回的一个属于 Vuex store 的对象,在用户输入时,v-model 会试图直接修改 obj.message。在严格模式中,由于这个修改不是在 mutations 函数中执行的, 这里会抛出一个错误。

方式一:给 <input> 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用一个方法。

<input :value="message" @input="updateMessage" />

<script>
  export default {
    computed: {
      ...mapState({
        message: (state) => state.obj.message
      })
    },
    methods: {
      updateMessage(e) {
        this.$store.commit('updateMessage', e.target.value)
      }
    }
  }
</script>

mutations 函数:

mutations: {
  updateMessage(state, message) {
    state.obj.message = message
  }
}

方式二:使用 setter 的双向绑定计算属性。

computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

严格模式

开启严格模式,仅需在创建 store 的时候传入 strict: true

const store = new Vuex.Store({
  // ...
  strict: true
})

在严格模式下,无论何时发生了状态变更且不是由 mutations 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

不要在生产环境下启用严格模式。严格模式会深度监测状态树来检测不合规的状态变更,请确保在生产环境下关闭严格模式,以避免性能损失。

类似于插件,我们可以让构建工具来处理这种情况:

const store = new Vuex.Store({
  // ...
  strict: process.env.NODE_ENV !== 'production'
})

常见的问题

谈谈你对 Vuex 的理解?

理解如下两张图,结合官网对 Vuex 的解释(Vuex 是什么?):

flow

vuex

什么时候使用 Vuex?

Vuex 通过全局注入 store 对象,来实现组件间的状态共享。

开发大型单页应用时(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理,这时候使用 vuex 比较合适。

如果只是简单的多个组件间传递数据,可以使用组件间常用的通信方法。

Vuex 的设计思想

Vuex 借鉴了 FluxReduxThe Elm Architecture,将数据存放到全局的 store,再将 store 挂载到每个 vue 实例组件中,利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。

Vuex 统一管理状态的好处

  • 能够在 Vuex 中集中管理共享的数据,易于开发和后期维护
  • 能够高效地实现组件之间的数据共享,提高开发效率
  • 存储在 Vuex 中的数据都是响应式的,能够实时保存数据与页面的同步

Mutations 和 Actions 有什么区别?

  • mutations 是 Vuex 用于修改 store 中状态的唯一方法,而 actions 只能通过 mutations 修改状态。
  • actions 可以包含任意异步操作,而 mutations 只能是同步操作。
  • 它们提交的方式不同,mutations 使用 commit 方法,而 actions 使用 dispatch 方法。

这些你都可以在官网找到 — MutationAction

更多资料

打造 Vue 技术栈中的“时间宝石“