vue.js ——Vuex

基本概念

vue进行开发过程中有没有遇到这样一种场景,就是有些时候一些数据是一种通用的共享数据(比如登录信息),那么这类数据在各个组件模块中可能都会用到,如果每个组件中都去后台重新获取那么势必会造成性能浪费,为了解决这一问题一个新的状态管理工具 - vuex就应运而生

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

什么是"状态管理模式"?

这个状态自管理应用包含以下几个部分:

  • 状态,驱动应用的数据源;
  • 视图,以声明方式将状态映射到视图;
  • 操作,响应在视图上的用户输入导致的状态变化。

安装方式

CDN引用

javascript 复制代码
https://unpkg.com/vuex@4

Unpkg.com 提供了基于 npm 的 CDN 链接。以上的链接会一直指向npm 上发布的最新版本。

您也可以通过 https://unpkg.com/vuex@4.0.0/dist/vuex.global.js 这样的方式指定特定的版本。

javascript 复制代码
Npm
npm install vuex@next --save
Yarn
yarn add vuex@next --save

核心概念

State

  • 单一状态树
    Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个"唯一数据源 (SSOT)"而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
  • 在 Vue 组件中获得 Vuex 状态
    由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
javascript 复制代码
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }}

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

Vuex 通过 Vue 的插件系统将 store 实例从根组件中"注入"到所有的子组件里。且子组件能通过this.$store访问到。让我们更新下Counter的实现:

javascript 复制代码
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }}
  • mapState 辅助函数
    当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。
    为了解决这个问题,我们可以使用 mapState辅助函数帮助我们生成计算属性,让你少按几次键
javascript 复制代码
import { mapState } from 'vuex'
export default {
  computed: mapState({
       count: state => state.count,  // 箭头函数可使代码更简练
    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',
    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })}
  • 对象展开运算符
    mapState函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给computed属性。但是自从有了对象展开运算符【...】,我们可以极大地简化写法:
javascript 复制代码
computed: {
  localComputed () { /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })}

对象展开运算符

var obj = {a:1,b:2}

var obj1 = {c:3,d:4}

var obj2 ={...obj,...obj1}

var obj3 = {...obj,a:8,w:66}

var obj4 ={...obj,a:8,w:66,...obj1}

console.log(obj); //{a: 1, b: 2}

console.log(obj1); //{c: 3, d: 4}

console.log(obj2); //{a: 1, b: 2,c: 3, d: 4}

console.log(obj3); //{a: 8, b: 2, w: 66}

console.log(obj4); //{a: 8, b: 2, w: 66, c: 3, d: 4}

Getter

我们需要从 store 中的 state 中派生出一些状态,如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它------无论哪种方式都不是很理想。

Vuex 允许我们在 store 中定义"getter"(可以认为是 store 的计算属性)。

从 Vue 3.0 开始,getter 的结果不再像计算属性一样会被缓存起来

Getter 接受 state 作为其第一个参数:

javascript 复制代码
const store = createStore({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos (state) {
      return state.todos.filter(todo => todo.done)
    }
  }})
  • 通过属性访问
    Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:
javascript 复制代码
store.getters.doneTodos 

Getter 也可以接受其他 getter 作为第二个参数

javascript 复制代码
getters: {
  // ...
  doneTodosCount (state, getters) {
    return getters.doneTodos.length
  }}
  • 通过方法访问
    你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
javascript 复制代码
getters: {
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }}
store.getters.getTodoById(2)

getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

  • mapGetters 辅助函数
    mapGetters辅助函数仅仅是将store中的getter映射到局部计算属性
javascript 复制代码
import { mapGetters } from 'vuex'
export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }}

Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

javascript 复制代码
const store = createStore({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }})
  • 提交载荷(Payload)
    你可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload):
javascript 复制代码
mutations: {
  increment (state, n) {
    state.count += n
  }}
store.commit('increment', 10)
  • 对象风格的提交方式
    提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
javascript 复制代码
store.commit({
  type: 'increment',
  amount: 10})

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此处理函数保持不变:

javascript 复制代码
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }}
  • Mutation 必须是同步函数
    一条重要的原则就是要记住 mutation 必须是同步函数

Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
  • 分发 Action
    Action 通过 store.dispatch 方法触发:
javascript 复制代码
store.dispatch('increment')
actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }}
  • 在组件中分发 Action
    你在组件中使用this.$store.dispatch('xxx')分发 action,或者使用mapActions辅助函数将组件的methods映射为store.dispatch调用(需要先在根节点注入store)
javascript 复制代码
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')`
    })
  }}

Module

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

为了解决以上问题,Vuex允许我们将store分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块

javascript 复制代码
const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }}

const store = createStore({
  modules: {
    a: moduleA,
    b: moduleB
  }})

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

进阶

项目结构

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

  • 应用层级的状态应该集中到单个 store 对象中。
  • 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  • 异步逻辑都应该封装到 action 里面。
    只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
    对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

热重载

使用 webpack 的 Hot Module Replacement API,Vuex 支持在开发过程中热重载 mutation、module、action 和 getter。你也可以在 Browserify 中使用 browserify-hmr 插件。

对于 mutation 和模块,你需要使用 store.hotUpdate() 方法:

javascript 复制代码
// store.js
import { createStore } from 'vuex'
import mutations from './mutations'
import moduleA from './modules/a'
const state = { ... }
const store = createStore({
  state,
  mutations,
  modules: {
    a: moduleA
  }})
if (module.hot) {
  // 使 action 和 mutation 成为可热重载模块
  module.hot.accept(['./mutations', './modules/a'], () => {
    // 获取更新后的模块
    // 因为 babel 6 的模块编译格式问题,这里需要加上 `.default`
    const newMutations = require('./mutations').default
    const newModuleA = require('./modules/a').default
    // 加载新模块
    store.hotUpdate({
      mutations: newMutations,
      modules: {
        a: newModuleA
      }
    })
  })}

动态模块热重载

如果你仅使用模块,你可以使用 require.context 来动态地加载或热重载所有的模块。

javascript 复制代码
// store.js
import { createStore } from 'vuex'
// 加载所有模块。function loadModules() {
  const context = require.context("./modules", false, /([a-z_]+)\.js$/i)
  const modules = context
    .keys()
    .map((key) => ({ key, name: key.match(/([a-z_]+)\.js$/i)[1] }))
    .reduce(
      (modules, { key, name }) => ({
        ...modules,
        [name]: context(key).default
      }),
      {}
    )

  return { context, modules }}
const { context, modules } = loadModules()
const store = new createStore({
  modules})
if (module.hot) {
  // 在任何模块发生改变时进行热重载。
  module.hot.accept(context.id, () => {
    const { modules } = loadModules()

    store.hotUpdate({
      modules
    })
  })}

组合式api

可以通过调用 useStore 函数,来在 setup 钩子函数中访问 store。这与在组件中使用选项式 API 访问 this.$store 是等效的。

javascript 复制代码
import { useStore } from 'vuex'
export default {
  setup () {
    const store = useStore()
  }}

访问 State 和 Getter

为了访问 state 和 getter,需要创建 computed 引用以保留响应性,这与在选项式 API 中创建计算属性等效。

javascript 复制代码
import { computed } from 'vue'import { useStore } from 'vuex'
export default {
  setup () {
    const store = useStore()

    return {
      // 在 computed 函数中访问 state
      count: computed(() => store.state.count),

      // 在 computed 函数中访问 getter
      double: computed(() => store.getters.double)
    }
  }}

访问 Mutation 和 Action

要使用 mutation 和 action 时,只需要在 setup 钩子函数中调用 commit 和 dispatch 函数。

javascript 复制代码
import { useStore } from 'vuex'
export default {
  setup () {
    const store = useStore()
    return {
      // 使用 mutation
      increment: () => store.commit('increment'),

      // 使用 action
      asyncIncrement: () => store.dispatch('asyncIncrement')
    }
  }}
相关推荐
咬人喵喵15 小时前
CSS Flexbox:拥有魔法的排版盒子
前端·css
LYFlied15 小时前
TS-Loader 源码解析与自定义 Webpack Loader 开发指南
前端·webpack·node.js·编译·打包
yzp011215 小时前
css收集
前端·css
暴富的Tdy15 小时前
【Webpack 的核心应用场景】
前端·webpack·node.js
遇见很ok15 小时前
Web Worker
前端·javascript·vue.js
风舞红枫15 小时前
前端可配置权限规则案例
前端
前端不太难15 小时前
RN Navigation vs Vue Router:从架构底层到工程实践的深度对比
javascript·vue.js·架构
zhougl99615 小时前
前端模块化
前端
暴富暴富暴富啦啦啦16 小时前
Map 缓存和拿取
前端·javascript·缓存
天问一16 小时前
前端Vue使用js-audio-plugin实现录音功能
前端·javascript·vue.js