Vuex看完这篇就够了

Vuex看完这篇就够了

哈喽,大家好,我是栗子为。

“我们在开发时经常会用到Vuex,前两天还在上大学的表弟问我Vuex是什么,我一整个震惊住,于是想那就写一篇笔记来详细的介绍一下吧。本篇适用于对Vue有一定使用经验的小伙伴!

大家都学习过Vue,那Vuex到底是什么呢?它又是如何使用的?本文将借助官方文档,为你详细介绍。相信大家看完此篇,就不会再有这样的疑惑了。”



01


Vuex是什么



首先附上官网的Vuex文档  https://vuex.vuejs.org/zh/


 官方文档中对Vuex概念的解释如下:

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

看一段来自官方的示例

const Counter = {
// 状态
data () {
return {
count: 0
}
},
// 视图
template: `
<div>{{ count }}</div>
`,
// 操作
methods: {
increment () {
this.count++
}
}
}
createApp(Counter).mount('#app')

上面代码的解释:

Counter组件中有count数据字段,定义了一个increment方法用来对字段进行加一操作,将结果渲染到template上,最后将这一组件挂载到app中

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

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

单向数据流简单示意图:

Vuex看完这篇就够了
单向数据流示意图

由于是单向数据流,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏,这会带来以下问题

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

这是来自官方的流程图

Vuex看完这篇就够了
流程图

安装

在这里主要介绍Vuex的两种安装方式

npm

npm install --save vuex@版本号
npm install --save vuex@3

yarn

yarn add --save vuex@版本号
yarn add --save vuex@3

当安装完成后package.json文件中会出现vuex及其版本

Vuex看完这篇就够了
相关依赖以及版本



02


核心概念



官方的解释

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的**状态 (state)**。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

在使用vuex之前,我们需要弄清楚如下几个核心概念

  • State
  • Getter
  • Mutation
  • Action
  • Module

State

我们可以简单理解为store(仓库)中的状态(也叫数据)

我们如何在 Vue 组件中展示状态呢?由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:

// 创建一个 Counter 组件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}

不足:这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。

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

const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}

mapState辅助函数

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

// 在单独构建的版本中辅助函数为 Vuex.mapState
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
}
})
}

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState传一个字符串数组。

computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])

mapState函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed属性。但是自从有了对象展开运算符,我们可以极大地简化写法:

computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}

Getter

简单理解可以是store实例的计算属性

看下面的示例

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)
}
}
})

通过属性访问

可以通过属性访问doneTodos的值

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Getter能接收其他Getter作为第二个参数

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

注意:getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。

通过方法访问

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

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

mapGetters辅助函数

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

import { mapGetters } from 'vuex'

export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}

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

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

Mutation

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

const store = createStore({
state: {
count: 1
},
mutations: {
increment (state,n) {
// 变更状态
state.count += n
}
}
})
store.commit('increment', 10)

Mutation必须是同步函数

一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子:

mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}

现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

mapMutations辅助函数

使用mapMutations辅助函数将组件中的 methods 映射为 store.commit调用(需要在根节点注入store)。

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')`
})
}
}

Action

Action 类似于 mutation,不同在于:

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

先看一个简单的示例:

const store = createStore({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment ({ commit }) { // 经常用到 ES2015 的参数解构来简化代码
commit('increment')
}
}
})

分发Action

看一段来自官网的购物车示例:

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)
)
}
}

这一段代码涉及到调用异步 API分发多重 mutation

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')`
})
}
}

组合Action

Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

通常的做法是利用async/await

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

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

Module

Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

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 的状态



03


Vuex使用流程



  1. 在src下新建store目录,分模块注册store
Vuex看完这篇就够了
分模块注册store
  1. index.js文件内容如下所示
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);
import home from './home'
import search from './search'


export default new Vuex.Store({
modules: {
home, // 导入模块
search
}
})
  1. home目录下的index.js内容如下
import { reqCategoryList } from '@/api'
// 仓库存储数据的地方
const state = {
categoryList: [],
};
// 修改state的唯一手段
const mutations = {
CATEGORYLIST(state, categoryList) {
state.categoryList = categoryList
},
};
// 处理action,可以书写自己的业务逻辑,也可以异步处理
const actions = {
async categoryList({ commit }) {
let result = await reqCategoryList()
if (result.code == 200) {
commit("CATEGORYLIST", result.data)
}
},
};
const getters = {};
export default {
state,
mutations,
actions,
getters
}
  1. component组件中的index.vue部分内容如下
<script>
import { mapState } from 'vuex'
export default {
name: 'typeNav',
//import引入的组件需要注入到对象中才能使用
components: {},
data() {
//这里存放数据
return {
};
},
//监听属性 类似于data概念
computed: {
...mapState({
categoryList: state => state.home.categoryList
})
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {
this.$store.dispatch("categoryList")
},
}
</script>
  1. 整体流程
  • 先在api文件夹下定义好接口,请求到数据,这些数据是要存储到store中的
  • 在home/index.js中引入定义好的接口方法
  • 定义一个categoryList的action,通过判断响应状态码是否为200,然后提交请求,同时可以携带参数
  • 在mutation中定义相应的方法,用来修改state中的状态
  • 在组件的js中,利用this.$store.dispatch来分发action获取到结果,利用computed计算属性来将内部对象存储到外部对象中



04


总结



以上就是小为在项目中使用Vuex的经验,有了专门的仓库来管理数据确实方便了许多,但Vuex仓库存储数据不是持久化的,希望大家看完能熟练使用这一插件。我们下期再见~

作者    栗子为   

编辑   一口栗子

原文始发于微信公众号(六只栗子):Vuex看完这篇就够了

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88705.html

(1)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!