首页与我联系

「vue基础」写给 Vuex 初学者的使用指南

By 前端达人
Published in 3-Vue
August 14, 2022
2 min read
「vue基础」写给 Vuex 初学者的使用指南

大家好,在前面的几篇文章里我们一起学习了Vue相关的基础知识,想复习基础的同学可以点击文末链接进行回顾。今天我们将学习 Vue 的 State Management(状态管理):Vuex,我们在构建大型应用时 ,State Management 的处理至关重要。

一、Vuex 简介

随着业务的增加,我们的应用程序也变得越来越复杂,每个组件都有自己的数据状态,再加上组件之间的数据传递问题,一个数据的变化会影响好几个组件的连锁反应,这就增加了我们定位问题的难度。

因此要解决上述问题,我们就要集中管理数据,在多个组件中共享数据状态时——比如用户的登录信息或者UI组件的呈现状态(按钮禁用或加载数据)。

幸运的是,我们不需要手工去完成 State Management 的工作,许多流行的框架都能帮助我们管理数据状态,你可能听说过Redux——React生态中比较流行的状态管理解决方案。Vue当然也有自己的官方解决方案,称作Vuex。他们共同的特点就是帮助我们集中管理数据状态以及任何组件无需要父子关系,也能很容易进行数据之间的交互。

二、Vuex 相关的几个概念术语

那我们如何使用Vuex呢?在使用之前,我们先看下下张图,这张图很好的诠释了 Vuex 的运行机制,理解了这张图,你就可以很快的上手Vuex的使用了。

要掌握Vuex理解以下几个概念很重要:

State

单一状态树,整个应用的数据源,组件的数据操作都会回流数据源,其是整个应用唯一的数据中心,相当一个数据仓库。

Store

数据中心的管家,我们首先需要在Vue里实例化在store对象,才能进行 Vuex 相关特性的应用,其中包含组件的共享状态state和改变状态的方法(暂且称作方法)mutations、Actions等。外部组件无法进行直接更改State,只能依赖dispatch action(行为调度) 或 commit a mutation(提交mutation)间接操作。

Getters

Getters 的本质就是 Vuex store 的 computed 属性,读取 store/state 的内容,Getters中的数据将会被缓存,如果你需要在多个数据中共享同样的数据,使用 Getters 将会是一个不错的选择。

Mutations

在应用中共享全局数据状态时,也会导致一些问题,因为数据的改变可以来自任何组件,因此很难定位和跟踪数据的状态。

因此 Vuex 提出了使用 Mutations 这种方式进行更改数据的状态,并不是直接进行更改,其 Vue devtools 工具能很好很准确帮我定位哪些更改以及何时进行的更改。

如果你使用过 Redux ,Mutations 的概念很类似 reducer,其作用也是对数据状态进行更改,更改 Vuex 的 store 中的数据状态的唯一方法是提交 mutation,但是仅限于同步的数据操作。

在外部组件调用 Mutations时,你需要使用 this.$store.commit(mutation) 方法进行调用,而不能直接进行调用。

Actions

Actions:类似于Mutation ,但是Mutations只能处理同步函数,而Actions则是可以处理任何的异步操作。Actions 提交的是 Mutations,而不是直接变更状态。也就是说,Actions会通过mutations,让mutations帮他提交数据的变更。比如我们常用的接口数据请求获取数据,就会经常用到Actions。

如果要执行异步任务或多个相关的Mutations去更新数据状态时,我们需要 Actions 去定义函数进行操作,Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,其可以获 state , commit 和 getters 的相关属性,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。因此在其内部我们能编写更复杂的业务逻辑。

最后做下总结,我们使用 Store/State 定义和管理应用的核心数据,如果在多个组件中共享同一个数据,我们可以创建compute属性调用 Getters 中的定义的方法。如果我们要异步或复制的操作数据,我们可以通过使用 dispatch 方法调用已注册的 Actions 方法,Actions 再去调用相关的 mutations 进行数据的操作。如果只是简单的数据操作,使用 this.$store.commit() 方法调用 Mutations 即可。

三、动手做一个简单例子

接下来我们亲自动手做一个简单的练习,通过代码进一步的了解Vuex,废话不多说,我们开始吧!

1、安装 Vuex

假设我们通过 CLI 工具创建了一个Vue 项目(使用默认预设),如果我们要使用 Vuex 就要安装相关依赖,安装命令如下:

npm install vuex

依赖安装完成后,我们需要将 Vuex 的 Store 实例进行注册,接下来我们在src目录里新建个 store.js ,示例代码如下:

src/store.js

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {}
});

我们在 Vuex.store 构造函数里传入一个对象,含有 state , mutations 及actions 这几个核心属性,不用担心,我们来一步步逐一实现,接下来我们打开 main.js 文件,在Vue全局实例里进行注册Store实例,示例代码如下:

src/main.js

import Vue from "vue";
import App from "./App.vue";
import store from "./store";
Vue.config.productionTip = false;
new Vue({
  store,
  render: h => h(App)
}).$mount("#app");

完成上述操作后,我们就能很方便的通过 this.$store 访问 store 实例内容。

2、在 State 里初始化数据

State 本身就是一个 JS 对象,创建的数据可以在不同的组件中进行共享,比如初始化一个购物车的数据,示例代码如下:

export default new Vuex.Store({
  state: {
    customerName: 'John Smith',
    shoppingCart: [
      {
        name: 'Jumbo Box of Teabags',
        quantity: 1,
        price: 350
      },
      {
        name: 'Packet of Fancy Biscuits',
        quantity: 1,
        price: 199
      },
    ]
  },
});

状态属性的值可以包含任何有效的JS数据类型,接下来我们可以在组件中使用 computed 进行数据的获取,比如我们要获取顾客的名字,示例代码如下:

<template>
  <div>
    <span>{{ customerName }}</span>
  </div>
</template>
<script>
export default {
  computed: {
    customerName() {
      return this.$store.state.customerName;
    }
  }
}
</script>

上述代码我们通过 store 实例进行数据获,也许你会觉得这样获取很啰嗦,Vuex 提供了一个工具函数能很方便的获取 store 实例的数据。

使用 mapState 方法,示例代码如下:

<template>
  <div>
    <span>{{ customerName }}</span>
  </div>
</template>
<script>
import { mapState } from "vuex";
export default {
  computed: {
    ...mapState(['customerName'])
  }
}
</script>

mapState() 方法使用 ES6 的新语法 … 帮助我们获取 State 中的数据,只需要在函数里传递State对应的属性即可,这样是不是很简单呢。

3、在 Getters 里获取数据

Getters 的本质就是 Vuex store 的 computed 属性,它允许你可以在不同组件之间共享数据状态,就和组件的 computed 属性是一样的,其中的数据将会被缓存,数据发生变化时,进行动态计算,相关调用的组件则试试反馈。

比如我们要获取购物车商品的商品种类的数量,示例代码如下:

export default new Vuex.Store({
  state: {
    shoppingCart: [
      // ...
    ]
  },
  getters: {
    cartItemCount: state => state.shoppingCart.length
  }
});

在组件中使用 getter 方法来获取 store/state ,我们需要创建一个 computed 属性进行调用,示例代码如下:

<template>
  <div>
    <span>Shopping Cart ({{ cartItemCount }} items)</span>
  </div>
</template>
<script>
export default {
  computed: {
    cartItemCount() {
      return this.$store.getters.cartItemCount;
    }
  }
}
</script>

同样 Vuex 提供了一个更便捷的方法 mapGetters() 快速调用 getter,我们传递getters 对象的属性值即可,示例代码如下:

<template>
  <div>
    <span>Shopping Cart ({{ cartItemCount }} items)</span>
  </div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
  computed: {
    ...mapGetters(['cartItemCount'])
  }
}
</script>

4、通过 Mutations 操作数据

如果我们要进行数据状态的更新,我们可以使用 Mutations 进行方法的定义,比如我们要更新购物车顾客的姓名,示例代码如下:

export default new Vuex.Store({
  state: {
    customerName: 'Fred'
  },
  mutations: {
    setCustomerName(state, name) {
      state.customerName = name;
    }
  }
});

接下来我们在调用的组件里定义方法,通过调用 mutations 的 setCustomerName 的方法进行数据操作,这里我们使用 commit() 方法进行调用,示例代码如下:

<template>
  <div>
    <p>{{ customerName }}</p>
    <input type="text" @input="updateName" :value="customerName" />
  </div>
</template>
<script>
import { mapState } from "vuex";
export default {
  name: "Example",
  computed: {
    ...mapState(['customerName'])
  },
  methods: {
    updateName(event) {
      this.$store.commit('setCustomerName', event.target.value);
    }
  }
}
</script>

上述代码,我们通过一个文本输入框组件,进行顾客姓名信息的更改,同样,你也猜到了,Vuex也提供了 mapMutations 方法,快速获取对应的属性方法,简化后的调用方法,示例代码如下:

import { mapState, mapMutations } from 'vuex';
export default {
  name: "Example",
  computed: {
    ...mapState(['customerName'])
  },
  methods: {
    ...mapMutations(['setCustomerName']),
    updateName(event) {
      this.setCustomerName(event.target.value);
    }
  }
}

你可能注意到,我们这里的操作是同步的,如果操作的数据需要等待,或者比较费时的操作,比如我们需要异步请求(AJAX)后端的数据来更新数据,我们就需要使用 Actions ,这就是其存在的理由。

5、使用 Actions 获取接口数据

讲到这里,你也许会这样理解,state 就好比 store/ state 的状态树,我们通过 commit 方法去调用mutations 定义的方法属性去更新数据状态,使用 getters 属性定义获取状态树的数据集合,用于多个组件的数据共享。

Actions 则为我们提供了异步获取后端数据API接口的规则,比如我们要获取一组用户列表信息,示例代码如下:

import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    users: [],
    isLoading: false,
  },
  mutations: {
    setLoadingTrue(state) {
      state.isLoading = true;
    },
    setLoadingFalse(state) {
      state.isLoading = false;
    },
    setUsers(state, users) {
      state.users = users;
    },
    setCustomerName(state, name) {
      state.customerName = name;
    }
  },
  actions: {
    getUsers(context) {
      context.commit('setLoadingTrue');
      axios.get('/api/users')
        .then(response => {
          context.commit('setUsers', response.data);
          context.commit('setLoadingFalse');
        })
        .catch(error => {
          context.commit('setLoadingFalse');
          // handle error
        });
    }
  }
});

在上述例子里,我们定义了数据请求中的状态,默认为false,请求数据时将其定义为true,请求完毕或接口异常时,将其重置为初始值。之所以定义这个状态值,方便前端组件进行UI的展示,提示用户数据正在加载中。

接下来我们可以通过 Vuex Store 提供的 this.$store.dispatch() 方法调用actions 定义的方法,但是也可以通过 mapActions() 来简化代码的调用,示例代码如下:

<template>
  <div>
    <div id="spinner" v-if="isLoading">
      <img src="spinner.gif" />
    </div>
    <ul v-else>
      <li v-for="(user, index) in users" :key="index" >{{ user }}</li>
    </ul>
  </div>
</template>
<script>
import { mapActions, mapState } from "vuex";
export default {
  computed: {
    ...mapState([
      'isLoading',
      'users'
    ])
  },
  methods: {
    ...mapActions(['getUsers'])
  },
  created() {
    this.getUsers();
  }
}
</script>

通过以上代码示例,想必大家对 State,Store,Getters, Mutations,Actions 有了更深刻的认识吧。

四、一个完整的项目示例

最后我们做一个完整的例子,对上述的学习进行一个巩固,我们来做一个用户信息列表和一个用户信息详细页,通过后端接口的形式进行获取更新数据源并进行显示。

我们先通过 CLI 脚手架,使用 manually 选项创建项目,确保我们选择了 Vue Router 和 Vuex 选项,创建完成后,我们修改下项目的 index.html 页面,添加一些基础的CSS样式信息。示例代码如下:

public/index.html

<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="viewport"
    content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <title>Vuex Example - Jump Start Vue.js</title>
    <link rel="stylesheet" type="text/css"
    href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.1/semantic.min.css">
    <style type="text/css">
      body {
        background-color: #FFFFFF;
      }
      .ui.menu .item img.logo {
        margin-right: 1.5em;
      }
      .main.container {
        margin-top: 7em;
      }
    </style>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

接着我们继续修改下 App 组件的内容,代码如下:

src/App.vue

<template>
  <div>
    <div class="ui fixed inverted menu">
      <div class="ui container">
        <div class="header item">
          <img class="logo" src="./assets/logo.png">
          Jump Start Vue.js
        </div>
        <router-link class="item" to="/" exact>Home</router-link>
        <router-link class="item" to="/users">Users</router-link>
      </div>
    </div>
    <router-view></router-view>
  </div>
</template>
<script>
import { mapActions } from "vuex";
export default {
  name: "App",
  methods: {
    ...mapActions(["fetchUsers"])
  },
  created() {
    this.fetchUsers();
  }
};
</script>

你可能注意到,上述代码我们创建了 router-link 组件,因为我们是多页面应用。同时我们调用了MapActions 中的 fetchUsers 方法 调用定义的Actions。应用一加载,我们就去请求后端数据,获取用户信息。

接下来,我们来编写Vuex的核心文件,store.js 文件,示例代码如下:

src/store.js

import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    users: [],
    selectedUserId: null,
    isFetching: false
  },
  mutations: {
    setUsers(state, { users }) {
      state.users = users;
    },
    setSelectedUser(state, id) {
      state.selectedUserId = id;
    },
    setIsFetching(state, bool) {
      state.isFetching = bool;
    }
  },
  getters: {
    selectedUser: state =>
      state.users.find(user => user.login.uuid === state.selectedUserId)
  },
  actions: {
    fetchUsers({ commit }) {
      commit("setIsFetching", true);
      return axios
        .get("https://randomuser.me/api/?nat=gb,us,au&results=5&seed=abc")
        .then(res => {
          setTimeout(() => {
            commit("setIsFetching", false);
            commit("setUsers", { users: res.data.results });
          }, 2500);
        })
        .catch(error => {
          commit("setIsFetching", false);
          console.error(error);
        });
    }
  }
});

上述代码,这里不再过多解释,因为和我们开头的例子很类似,这里需要提一下,我们需要通过以下命令安装 axios 依赖:

npm install axios

接下来,我们继续编写三个页面组件:Home(首页)、Users(用户列表)、User(用户信息页)

src/views/Home.vue

<template>
  <div class="ui main text container">
    <h1 class="ui header">Vuex 数据管理</h1>
    <p>This is a basic Vuex example app, to demo the concepts learned in the
    ➥accompanying chapter.</p>
    <p>Go to <router-link to="/users">Users</router-link></p>
  </div>
</template>
<script>
export default {
  name: "Home"
}
</script>

上面的代码也不需要太多的解释,首页包含了一个链接,导向用户信息列表页。

src/views/Users.vue

<template>
  <div class="ui main text container">
    <h1 class="ui header">Users</h1>
    <div class="ui active inverted dimmer" v-if="isFetching">
      <div class="ui text loader">Loading</div>
    </div>
    <ul v-else>
      <li v-for="(user, index) in users" :key="index">
        <router-link :to="{ name: 'user', params: { id: user.login.uuid }}">
          {{ user.name.title }} {{ user.name.first }} {{ user.name.last }}
        </router-link>
      </li>
    </ul>
  </div>
</template>
<script>
import { mapState } from "vuex";
export default {
  name: "Users",
  computed: {
    ...mapState([
      'isFetching',
      'users'
    ])
  }
}
</script>
<style>
  li {
    text-transform: capitalize;
  }
</style>

上述代码,我们通过 MapState 获取了 isFetching,users 数据状态,第一个用于显示数据是否正在加载中(接口是否正在请求中),第二个则是用户的数据信息的集合。并含有一个链接指向用户信息详情页。

src/views/User.vue

<template>
  <div class="ui main text container" v-if="selectedUser">
    <div class="ui items">
      <div class="item">
        <div class="image">
          <img :src="selectedUser.picture.large">
        </div>
        <div class="content">
          <a class="header">{{ fullName }}</a>
          <div class="meta">
            <span>{{ selectedUser.email }}</span>
          </div>
          <div class="description">
            <p>{{ selectedUser.location.street }}, {{ selectedUser.location.city }},
            {{ selectedUser.location.state }}, {{ selectedUser.location.postcode }}
            </p>
          </div>
          <div class="extra">
            {{ selectedUser.phone }}<br />
            {{ selectedUser.cell }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { mapGetters, mapMutations } from "vuex";
export default {
  name: "Users",
  computed: {
    ...mapGetters(["selectedUser"]),
    fullName() {
      return `${this.selectedUser.name.first} ${this.selectedUser.name.last}`;
    }
  },
  methods: {
    ...mapMutations(["setSelectedUser"])
  },
  created() {
    const userId = this.$route.params.id;
    this.setSelectedUser(userId);
  }
};
</script>
<style scoped>
  a.header, p {
    text-transform: capitalize;
  }
</style>

这个组件通过路由传参,调用 Mutations 的方法,更新当前的用户的数据状态信息,并通过mapGetters 方法获取 selectedUser 定义的属性方法,读取用户的信息。

最后我们来看下路由组件的定义,示例代码如下:

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import Users from "./views/Users.vue";
import User from "./views/User.vue";
Vue.use(Router);
export default new Router({
  mode: "history",
  linkActiveClass: "active",
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    },
    {
      name: "users",
      path: "/users",
      component: Users
    },
    {
      name: "user",
      path: "/users/:id",
      component: User
    }
  ]
});

最后完成的项目效果如下图所示:

五、小节

今天的分享就到这里,最后我们在做下小节:

  • state 是一个JS对象,包含了整个应用程序中需要共享的数据,在组件中,我们可以创建computed 属性调用 Vuex 提供的 mapState 函数获取数据
  • Getters 本质是 Vuex Store 内部的 computed 计算属性,它允许你在不同的组件之间共享相同的数据,在需要调用的组件里,我们创建 computed 属性,调用 mapGetters() 获取对应的属性方法即可。
  • 组件不会直接去更改数据中心的内容,当我们需要更新数据状态时,需要使用 Store 提供的commit() 方法调用Mutations定义的属性方法即可。你也可以使用 mapMutations 的方法进行调用。有点需要注意的是,这里的方法只是同步调用。
  • Actions 永远不会直接去操作 state 中的数据,而是执行一些组合逻辑,通常是异步的操作逻辑,将数据的操作委托给 mutations ,让mutations进行调用。Actions 内部的方法,接受一个与 store 实例具有相同方法和属性的 context ,此参数对象包含了当前的 state , commit , 和 getter,你能很方便的组织复杂的逻辑。和 Mutations 一样我们不能直接调用 Actions 里定义的方法,而是需要借助 this.$store.dispatch() 这个调度方法,除了这个方法,你还可以使用 mapActions() 进行更便捷的调用。

六、Vue 基础相关文章

「vue基础」新手快速入门篇

「vue基础」Vue相关构建工具和基础插件简介

「vue基础」手把手教你编写 Vue 组件(上)

「vue基础」手把手教你编写 Vue 组件(下)

「vue基础」Vue Router 使用指南上篇

「vue基础」Vue Router 使用指南下篇

本文大部分内容翻译来源:《Jump Start Vue.js》 作者:Nilson Jacques 链接: https://www.sitepoint.com/premium/books/jump-start-vue-js/read/5

qrcode.jpg

注:本文属于原创文章,版权属于「前端达人」公众号及 qianduandaren.com 所有,未经授权,谢绝一切形式的转载


Tags

vuebasicvuex
Previous Article
手写原生代码专题 | 三角板 Loading 效果 和 骨架屏图片卡片预加载效果(三)
前端达人

前端达人

专注前端知识分享

Table Of Contents

1
一、Vuex 简介
2
二、Vuex 相关的几个概念术语
3
三、动手做一个简单例子
4
四、一个完整的项目示例
5
五、小节
6
六、Vue 基础相关文章

相关文章

「短文」如何在 Vue 中复制文本到剪贴板
November 04, 2022
1 min

前端站点

VUE官网React官网TypeScript官网

公众号:前端达人

前端达人公众号