Vuex 是 Vue.js 生态系统中必不可少的工具。但是新接触 Vuex 的开发人员可能会被诸如“状态管理模式”这样的术语所排斥,并且对他们实际需要Vuex的目的感到困惑。
本文算是 Vuex的入门,当然也会 Vuex 的高级概念,并向大家展示如何在应用程序中使用 Vuex。
Vuex 解决的问题
要理解Vuex,首先要理解它要解决的问题。
假设我们开发了一个多用户聊天应用。界面有用户列表、私人聊天窗口、带有聊天记录的收件箱和通知栏,通知用户当前未查看的其他用户的未读消息。
数以百万计的用户每天通过我们的应用与数以百万计的其他用户聊天。然而,有人抱怨一个恼人的问题:通知栏偶尔会给出错误的通知。用户被通知有一条新的未读消息,但当他们查看时,它只是一条已经被看过的消息。
该作者所描述的是几年前 Facebook 开发人员在其聊天系统中遇到的真实情况。解决这一问题的过程中 开发人员创建名为 **"Flux"**的应用程序体系结构。Flux 构成了Vuex,Redux 和其它类似库的基础。
Flux
Facebook开发者为“僵尸通知”这个问题,苦苦挣扎了一段时间。他们最终意识到,它的持久性不仅仅是一个简单的缺陷——它指出了应用程序架构中的一些潜在缺陷。
抽象中最容易理解该缺陷:当应用程序中有多个共享数据的组件时,它们互连的复杂性将增加到无法预测或理解数据状态的地步。因此,该应用程序无法扩展或维护。
Flux 是一个模式,不是一个库。我们不能去Github下载 Flux。它是一种类似MVC的设计模式。像Vuex和Redux这样的库实现Flux模式的方式与其他框架实现MVC模式的方式相同。
事实上,Vuex并没有实现Flux的全部,只是一个子集。不过现在不要担心这个问题,我们关注于理解它所遵循的关键原则。
原则1:单一来源
组件可能具有仅需要了解的本地数据。例如,滚动条在用户列表组件中的位置可能与其他组件无关。
但是,要在组件之间共享的任何数据(即应用程序数据)都必须保存在一个单独的位置,与使用它的组件分开。
这个单一位置称为 "store"。组件必须从该位置读取应用程序数据,并且不能保留其自己的副本以防止冲突或分歧。
- import { createStore } from "vuex";
- // Instantiate our Vuex store
- const store = createStore({
- // "State" 组件的应用程序数据
- state () {
- return {
- myValue: 0
- };
- }
- });
- // 组件从其计算的属性访问 state
- const MyComponent = {
- template: "<div>{{ myValue }}</div>",
- computed: {
- myValue () {
- return store.state.myValue;
- }
- }
- };
原则2:数据是只读的
组件可以从store中自由读取数据。但是不能更改 store 中的数据,至少不能直接更改。
取而代之的是,它们必须告知 store 要更改数据的意图,store由负责通过一组定义的功能(称为mutation)进行更改。
为什么采用这种方法?如果我们集中数据更改逻辑,那么在状态不一致的情况下,我们只需要在同一地方排查就行了,不用到具体的每个文件中。我们将某些随机组件(可能在第三方模块中)以意外方式更改数据的可能性降至最低。
- const store = createStore({
- state() {
- return {
- myValue: 0
- };
- },
- mutations: {
- increment (state, value) {
- state.myValue += value;
- }
- }
- });
- // 需要更新值吗?
- // 错误的,不要直接更改 store 值
- store.myValue += 10;
- // 正确的,调用正确的 mutations。
- store.commit('increment', 10);
原则3:mutation 是同步的
如果应用程序在其体系结构中实现了上述两个原则,那么调试数据不一致就容易得多。可以记录提交并观察状态如何变化(在使用Vue Devtools 时确实可以这样做)。
但如果我们的mutation被异步调用,这种能力就会被削弱。我们知道提交的顺序,但我们不知道组件提交它们的顺序。
同步mutation可确保状态不取决于不可预测事件的顺序和时间。
太酷了,那么 Vuex 到底是什么?
有了所有这些背景知识,我们终于可以解决这个问题-Vuex 是一个库,可帮助我们在Vue应用程序中实现Flux架构。通过执行上述原则,即使在多个组件之间共享数据时,Vuex 仍可将我们的应用程序数据保持在透明且可预测的状态。
现在,我们已经对Vuex有了一个高级的了解,我们看看如何在实际项目创建基于Vuex的应用程序。
做一个使用 Vuex to-do-list
为了演示Vuex的用法,我们设置一个简单的待办应用程序。大家可以在此处访问代码的有效示例。
示例地址:https://codesandbox.io/s/happy-williams-rdni7
如果大家自己的电脑尝试一波,那么可以使用下面的命令:
- vue create vuex-example
安装 Vuex
- cd vuex-example
- npm i -S vuex@4
- npm run serve
创建一个 Vuex store
现在,创建 Vuex store,在项目中创建 src/store/index.js:
- mkdir src/store
- touch src/store/index.js
打开文件并导入createStore方法。此方法用于定义store及其特性。现在,我们导出该store ,以便在Vue应用中能访问它。
- // src/store/index.js
- import { createStore } from "vuex";
- export default createStore({});
将 store 添加到 Vue 实例
为了可以从任何组件中访问 Vuex store,我们需要在主文件中导入 store 模块,并将store作为插件安装在主Vue实例上
- // src/main.js
- import { createApp } from "vue";
- import App from "@/App";
- import store from "@/store";
- const app = createApp(App);
- app.use(store);
- app.mount("#app");
创建一个简单的应用程序
如上所述,Vuex 的重点是通常在大型应用程序中创建可扩展的全局状态。但是,我们可以在一个简单的待办应用程序中演示其功能。
完成后效果如下所示:
现在,删除 HelloWorld 文件:
- rm src/components/HelloWorld.vue
TodoNew.vue
现在,添加一个新组件 TodoNew,它负责创建新的待办事项。
- touch src/components/TodoNew.vue
打开 TodoNew.vue 并输入以下内容:
- // src/components/TodoNew.vue
- <template>
- <form @submit.prevent="addTodo">
- <input
- type="text"
- placeholder="Enter a new task"
- v-model="task"
- />
- </form>
- </template>
现在转到组件定义,有两个局部状态属性-task和给新待办事项提供唯一标识符的id。
- // src/components/TodoNew.vue
- <template>...</template>
- <script>
- export default {
- data() {
- return {
- task: "",
- id: 0
- };
- },
- methods: {
- addTodo: function() {
- //
- }
- }
- };
- </script>
定义 store 状态
过会,我们会创建一个显示待办事项的组件。由于它和TodoNew组件都需要访问相同的数据,因此这是我们在 Vuex 存储中保存的全局state 的理想选择。
现在,回到state并定义属性状态。这里使用 Vux4 提供的 createStore 函数,该函数返回一个对象。该对象具有一个属性 todos,它是一个空数组。
- // src/store/index.js
- import { createStore } from "vuex";
- export default createStore({
- state () {
- return {
- todos: []
- }
- }
- });
定义 mutation
从原则2可以知道,Vuex state 不能直接更改,需要定义mutator函数。
现在,我们向store添加一个mutation属性,并添加一个函数属性addTodo。所有mutator方法第一个参数。第二个可选参数是 store,第二个是传递的数据。
- // src/store/index.js
- import { createStore } from "vuex";
- export default createStore({
- state () {
- return {
- todos: []
- }
- },
- mutations: {
- addTodo (state, item) {
- state.todos.unshift(item);
- }
- }
- });
使用 commit 调用 mutation
现在,可以在TodoNew组件中使用它,在 TodoNew组件定义一个addTodo方法。
要访问store ,我们可以使用全局属性this.$store。使用commit方法创建一个新的mutation。需要传递了两个参数-首先是mutation的名称,其次是我们要传递的对象,是一个新的待办事项(由id和task值组成)。
- // src/components/TodoNew.vue
- methods: {
- addTodo: function() {
- const { id, task } = this;
- this.$store.commit("addTodo", { id, task });
- this.id++;
- this.task = "";
- }
- }
回顾
到目前为止:
- 用户将待办事项通过输入框输入,并绑定到 task 变量。
- 提交表单后,将调用addTodo方法
- 创建一个待办事项对象并将其“提交”到store中。
- // src/components/TodoNew.vue
- <template>
- <form @submit.prevent="addTodo">
- <input
- type="text"
- placeholder="Enter a new task"
- v-model="task"
- />
- </form>
- </template>
- <script>
- export default {
- data() {
- return {
- task: "",
- id: 0
- };
- },
- methods: {
- addTodo: function() {
- const { id, task } = this;
- this.$store.commit("addTodo", { id, task });
- this.id++;
- this.task = "";
- }
- }
- };
- </script>
读取 store 数据
现在,我们已经创建了用于添加待办事项的功能。接下来,就是把它们显示出来。
创建一个新组件TodoList.vue 文件。
- touch src/components/TodoList.vue
内容如下:
- // src/components/TodoList.vue
- <template>
- <ul>
- <li
- v-for="todo in todos"
- :key="todo.id"
- >
- {{ todo.description }}
- </li>
- </ul>
- </template>
todos是一个计算属性,我们在其中返回Vuex store 的内容。
- // src/components/TodoList.vue
- <script>
- export default {
- computed: {
- todos() {
- //
- }
- }
- };
- </script>
定义 getters
与直接访问store 内容不同,getter是类似于存储的计算属性的函数。在将数据返回到应用程序之前,这些工具非常适合过滤或转换数据。
例如,下面有getTodos,它返回未过滤的状态。在许多情况下,可以使用filter或map来转换此内容。
todoCount返回todo数组的长度。
通过确保组件愿意保留数据的本地副本,getter有助于实现原则1,即单一数据来源。
- // src/store/index.js
- export default createStore({
- ...
- getters: {
- getTodos (state) {
- return state.todos;
- },
- todoCount (state) {
- return state.todos.length;
- }
- }
- })
返回TodoList组件,我们通过返回this.$store.getters.getTodos来完成功能。
- // src/components/TodoList.vue
- <script>
- export default {
- computed: {
- todos() {
- return this.$store.getters.getTodos;
- }
- }
- };
- </script>
App.vue
要完成此应用程序,现在要做的就是导入并在App.vue中声明我们的组件。
- // src/App.vue
- <template>
- <div>
- <h1>To-Do List</h1>
- <div>
- <TodoNew />
- <TodoList />
- </div>
- </div>
- </template>
- <script>
- import TodoNew from "@/components/TodoNew.vue";
- import TodoList from "@/components/TodoList.vue";
- export default {
- components: {
- TodoNew,
- TodoList
- }
- };
- </script>
你真的需要Vuex吗?
显然,在大型应用程序中,拥有全局状态管理解决方案将有助于让我们的应用程序可预测和可维护。
但对于比较小的项目,有时候觉得使用 Vuex 太大材小用了,还这个还是大家跟着实际需求走比较合理。
Vuex的优点:
- 易于管理全局状态
- 强大的调试全局状态
Vuex的缺点:
- 额外的项目依赖
- 繁琐的模板
~ 完, 我是刷碗智,刷碗去咯,下期见!
作者:Anthony Gore 译者:前端小智 来源:vuejsdevelopers
原文:https://vuejsdevelopers.com/2017/05/15/vue-js-what-is-vuex/