【Vue2】【Vuex】状态管理方式

156 阅读4分钟

抛砖引玉

image.png

上图中Header、Slider和Bottom属于同层级的的组件,Content属于路由组件。

Header、Slider和Bottom可以通过Props、自定义事件和provide/inject实现组件间的通信,但是Content与以上三个组件没有直接关联关系,它们之间的相互通信是隔绝,我们可以使用provide/inject实现路由子组件到父组件的通信,但是父组件到路由子组件的通信就做不到了。

那么,我们就需要一个路由子组件与页面父组件和兄弟组件的沟通桥梁,如此,Vuex就很好的解决了我们这个问题。

Vuex的介绍

Vuex,状态管理方式,专门为Vue应用程序开发的工具,采用集中式存储管理应用的所有组件的状态,很好的解决了所有组件(路由子组件与页面父组件和兄弟组件)之间的相互通信问题。

它就相当于是所有组件的一个大型、统一管理的数据仓库,管理着所有组件的共享状态,它们都可以从这里获取数据或触发行为,同时其它相应组件也可以实时监听到数据的变更。

特点
1)Vuex应用的核心是store(仓库),每个应用只有一个 store 实例
2)Vuex的状态存储是响应式的,当组件从store读取状态时,其他组件更新了状态值,那么相应组件读到的状态也会得到高效更新。简单讲,就是state被更新,但凡是读取了state的组件都会得到实时更新。
3)为了方便我们跟踪每一个状态的变化,不能直接修改store中的状态,只能显式提交(commit)mutation来修改。示例:this.store.commit("updatedStateMethod", "这是更新值");

Vuex的核心要素

Vuex有5大核心要要素:State、Getters、Mutation、Actions、Modules。

1. State

单一树状态,用来注册(初始化)共享状态,作用类似于组件中的data。

注册(初始化)方式

export default {
  state: {
    todoList: [],
    count: 0,
  },
}

组件内访问方式

// ...
export default {
  // ...
  // 监听
  watch: {
    "$store.state.message.todoList": {
      deep: true,
      handler(val) {
        console.log(val);
      },
    },
    "$store.state.message.count"(val) {
      console.log(val);
    }
  },
  // 计算属性
  computed: {
    // 直接访问
    count() {
      return this.$store.state.message.count;
    },
    // mapState辅助函数
    ...mapState({
       "todoList",
       // 箭头函数可使代码更简练
       count: state => state.count,
       // 传字符串参数 'count' 等同于 `state => state.count`
       countAlias: 'count',
       // 为了能够使用 `this` 获取局部状态,必须使用常规函数
       countPlusLocalState (state) {
        return state.count + this.localCount
      }
    })
  },
  // 方法
  methods: {
    func() {
      this.todoList = this.$store.state.message.todoList;
    },
  }
}

2. Getters

用来计算共享状态的属性,作用类似于组件中的计算属性computed,可以直接理解是store的computed。
例如:在多个组件内都使用到了如下方法,我们无非就是复制粘贴或提取为一个共享方法然后多处导入,但是两个都不是最理想的。

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

Vuex提供的getters就很好的解决了这个问题。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

示例

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    // Getter 接受 state 作为其第一个参数
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
    // Getter 也可以接受其他 getter 作为第二个参数
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
    },
    // 也可以通过返回一个函数,来实现给getters传参(使用的时候可以缩写,我这为了看起来明了就没有缩写)
    getTodoById: (state) => {
       return (id) => {
         return state.todos.find(todo => todo.id === id)
       }
    }
  }
})

在组件内部我们可以通过属性方法mapGetters辅助函数三种方式获取getters的计算。

通过属性访问

  • this.$store.getters.doneTodos
  • this.$store.getters.doneTodosCount

通过方法访问:

  • this.$store.getters.getTodoById(2)
    注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

mapGetters辅助函数: 使用对象展开运算符将 getter 混入 computed 对象中:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ]),
  }
}

想将一个 getter 属性另取一个名字,使用对象形式:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
    ...mapGetters({
      // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
      doneCount: 'doneTodosCount'
    })
  }
}

3. Mutation

用来修改数据。 修改store中的状态的唯一方法是提交 Mutation
原则:必须是同步函数

Mutation的注册方式和传参:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    // 会接受state作为第一个参数
    increment (state) {
      // 变更状态
      state.count++
    },
    // 传入额外的参数,即 mutation 的 载荷(payload)。
    increment1 (state, n) {
      state.count += n;
    },
    // 载荷(payload)的字符类型可以是字符串、数字、对象等,具体可以根据实际情况
    increment2 (state, payload) {
      state.count += payload.num;
    },
    //
    increment3 (state, payload) {
      state.count += payload.num;
    },
  }
})

Mutation的提交方式:

// 在组件内部用mapMutations提交时
import { mapMutations } from 'vuex'
export default {
  // ...
  methods: {
    // 直接提交
    userCommit() {
      this.$store.commit('increment'); // 不传参
      this.$store.commit('increment1', 10); // 传参类型是数字
      this.$store.commit('increment2', { num: 10 }); // 传参类型是对象
      this.$store.commit({ type: "increment", num: 10 }); // 使用对象风格方式提交
    },
    // 使用mapMutations提交
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

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

// 在定义store文件中提交时
store.commit('increment'); // 不传参
store.commit('increment1', 10); // 传参类型是数字
store.commit('increment2', { num: 10 }); // 传参类型是对象
store.commit({ type: "increment", num: 10 }); // 使用对象风格方式提交

4. Actions

Actions类似于mutation,区别在于:

  • Actions提交的是mutation,而不是直接变更状态;
  • Actions可以包含任意异步操作;

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    },
    incrementPayload (state, data) {
      state.count += data.num;
    },
  },
  actions: {
    // 简单使用
    increment (context) {
      context.commit('increment')
    },
    // 使用到ES6的参数解构来简化代码
    incrementES6 ({ commit }) {
      commit('incrementES6')
    },
    // 可以执行异步操作
    incrementAsync ({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    },
    // 载荷
    incrementPayload({ commit }, data) {
      setTimeout(() => {
        commit('increment', data)
      }, 1000)
    },
  }
})

分发(触发)Action:

export default {
  // ...
  methods: {
    useActionsFun() {
      // 简单使用
      this.$store.dispatch('increment');
      // 对象方式
      this.$store.dispatch({ type: "incrementES6" });
      // 载荷形式
      this.$store.dispatch('incrementPayload', { num: 10 });
    },
  }
}

5. Modules

模块拆分。为了解决应用变得非常复杂时,store 对象就有可能变得相当臃肿的问题。

每个模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'

Vue.use(Vuex)
let store = new Vuex.Store({
  // 1. state
  state: {},
  // 2. getters
  getters: {},
  // 3. actions
  // 通常跟api接口打交道
  actions: {},
  // 4. mutations,通常用于同步的,更新state数据
  mutations: {},
  modules: {
    app,
  }
})

export default store


// app.js
export default {
  state: {},
  getters: {},
  actions:{},
  mutations: {},
}

Vuex的学习资料

  1. 官方资料:

    Vuex

    Vuex的API

    Github

  2. 优秀博客:

    vue中使用vuex(超详细)