这是关于 JavaScript 框架 Vue.js 的五部分系列的第四部分。 在这一部分中,我们将介绍用于状态管理的 Vuex。 这并非旨在成为完整的指南,而是概述基础知识以帮助您快速上手,以便您能够了解 Vue.js 并理解该框架提供的功能。

Vuex
如果您错过了关于组件和 Vue-cli 的前几个部分,您可能需要在继续阅读之前复习一下。 现在我们已经了解了组件以及传递状态和道具的基本知识,让我们谈谈 Vuex。 它是用于状态管理的实用工具。
之前,我们已将状态从顶层组件传递下来,并且兄弟组件不共享数据。 如果它们需要相互通信,我们将不得不将状态向上推送到应用程序中。 这有效! 但一旦您的应用程序达到一定的复杂程度,这种做法就不再合理。 如果你以前使用过 Redux,那么所有这些概念和实现对你来说都很熟悉。 Vuex 基本上是 Vue 的 Redux 版本。 事实上,Redux 也适用于 Vue,但使用 Vuex,您将受益于使用专为您的框架而设计的工具。
首先,我们将安装 Vuex
npm install vuex
或
yarn add vuex
我以这种方式设置:在我的 `/src` 目录中,我创建另一个名为 store 的目录(这是一个偏好,您也可以在同一目录中创建一个 `store.js` 文件),以及一个名为 `store.js` 的文件。 `store.js` 中的初始设置将类似于这样(vstore sublime 代码片段)
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
key: value
}
});
key: value
是任何类型的状态数据的占位符。 在其他示例中,我们使用的是 counter: 0
。
在我们的 `main.js` 文件中,我们将执行以下更新(突出显示更新的行)
import Vue from 'vue';
import App from './App.vue';
import { store } from './store/store';
new Vue({
el: '#app',
store: store,
template: '<App/>',
components: { App }
});
设置完成后,我们可以将我们的 `data()` 作为状态放置在文件中,就像我们之前对组件所做的那样,然后我们将使用此状态或使用以下三种方式更新它
- Getter 将使值能够在我们的模板中静态显示。 换句话说,getter 可以读取值,但不能改变状态。
- Mutation 将允许我们更新状态,但它们始终是同步的。 Mutation 是更改 store 中状态数据的唯一方法。
- Action 将允许我们异步更新状态,但将使用现有的 mutation。 如果您需要按特定顺序执行几个不同的 mutation,这将非常有用。
如果您以前从未使用过异步状态更改,那么有时很难理解为什么要使用它,因此,让我们首先概述一下它在抽象中的执行方式,然后在下一节中深入研究实际内容。 假设您是 Tumblr。 您在一个页面上有大量的重型 gif,该页面很长很长。 您只希望一次加载一定数量的 gif,例如 20 个,直到用户距离原始页面底部 200px 的位置。
您可以有一个显示接下来 20 个 gif 的 mutation。 但是您还没有接下来的 20 个 gif,也不知道何时到达页面底部。 因此,在应用程序本身中,您创建了一个侦听滚动位置的事件,并触发了一个 action。
然后,action 从数据库中检索接下来的 20 个图像的 URL,并包装 mutation,该 mutation 将 20 个图像添加到状态并显示它们。
Action 本质上创建了一个用于请求数据的框架。 它们提供了一种一致的方法来以异步方式应用数据。
最基本的抽象示例
在下面的示例中,我们展示了每个的**最基本实现**,以便您了解设置及其工作原理。 Payload 是一个可选参数。 您可以通过它定义更新组件的数量。 不用担心,我们稍后会使用实际演示,重要的是首先要掌握基础概念。
在 `store.js` 中
export const store = new Vuex.Store({
state: {
counter: 0
},
//showing things, not mutating state
getters: {
tripleCounter: state => {
return state.counter * 3;
}
},
//mutating the state
//mutations are always synchronous
mutations: {
//showing passed with payload, represented as num
increment: (state, num) => {
state.counter += num;
}
},
//commits the mutation, it's asynchronous
actions: {
// showing passed with payload, represented as asynchNum (an object)
asyncDecrement: ({ commit }, asyncNum) => {
setTimeout(() => {
//the asyncNum objects could also just be static amounts
commit('decrement', asyncNum.by);
}, asyncNum.duration);
}
}
});
这里一个非常好的功能是,我们可以在 mutation 中返回整个状态对象,但我们**不必**这样做,我们只需使用我们需要的部分即可。 时间旅行调试(逐步遍历 mutation 以查找错误)无论哪种方式都将继续工作。
在组件本身中,我们将使用 `computed` 来表示**getter**(这是有道理的,因为该值已经为我们计算出来),并使用 `methods` 和 `dispatch` 来访问**mutation 和 action**
在 `app.vue` 中
computed: {
value() {
return this.$store.getters.value;
}
},
methods: {
increment() {
this.$store.dispatch('increment', 2)
}
}
或者,您可以使用展开运算符。 我发现这在您需要使用大量 mutation/action 时很有用
export default {
// ...
methods: {
...mapActions([
'increment', // map this.increment() to this.$store.commit('increment')
'decrement',
'asyncIncrement'
])
}
}
简单的真实示例
让我们再次看一下天气通知应用程序,其中 Vuex store 中的状态非常少且简单。 这是仓库.
查看 CodePen 上 Sarah Drasner (@sdras) 的 Vue 天气通知。
在 `store.js` 中
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
showWeather: false,
template: 0
},
mutations: {
toggle: state => state.showWeather = !state.showWeather,
updateTemplate: (state) => {
state.showWeather = !state.showWeather;
state.template = (state.template + 1) % 4;
}
}
});
在这里,我们设置了 `showWeather` 的状态,它最初设置为 false,因为我们不希望任何动画立即触发,只有当用户点击手机按钮时才会触发。 在 mutation 中,我们设置了 `showWeather` 状态的切换。
我们还在状态中将 `template` 设置为 0。 我们将使用这个数字来逐个循环遍历每个天气组件。 因此,在 mutation 中,我们创建了一个名为 `updateTemplate` 的方法。 此方法既切换 `showWeather` 的状态,又将 `template` 更新为下一个数字,但当它达到数字 4 时,它将循环回到 0。
在 App.vue 中
<template>
<div id="app">
...
<g id="phonebutton" @click="updateTemplate" v-if="!showWeather">
...
</g>
<transition
@leave="leaveDroparea"
:css="false">
<g v-if="showWeather">
<app-droparea v-if="template === 1"></app-droparea>
<app-windarea v-else-if="template === 2"></app-windarea>
<app-rainbowarea v-else-if="template === 3"></app-rainbowarea>
<app-tornadoarea v-else></app-tornadoarea>
</g>
</transition>
...
</div>
</template>
<script>
import Dialog from './components/Dialog.vue';
...
export default {
computed: {
showWeather() {
return this.$store.state.showWeather;
},
template() {
return this.$store.state.template;
}
},
methods: {
updateTemplate() {
this.$store.commit('updateTemplate');
}
},
...
components: {
appDialog: Dialog,
...
}
}
</script>
在 `dialog.vue` 中
<script>
export default {
computed: {
template() {
return this.$store.state.template;
}
},
methods: {
toggle() {
this.$store.commit('toggle');
}
},
mounted () {
//enter weather
const tl = new TimelineMax();
...
}
}
</script>
在上面的代码中,App 使用 `showWeather` 来推进模板,而 Dialog 只是切换组件可见性。 您还可以看到,在 App.vue 中,我们正在根据 App `<template>` 中的 `template` 值显示和隐藏不同的子组件,方法是使用我们在第一篇文章中学习到的巧妙的条件渲染。 在 App 中,我们既通过 `computed` 值侦听 store 中状态的变化,又使用 `toggle()` 和 `updateTemplate()` 方法提交到 store 的 mutation 中。
这是一个基本示例,但您可以看到,对于一个包含大量状态的复杂应用程序,将所有状态管理在一个地方,而不是将其在组件之间上下移动,会非常有用。 特别是当兄弟组件需要相互通信时。
如果您有兴趣深入研究 Vuex,请访问 此处提供的出色文档。 您可能已经注意到,我们在最后一个演示中使用了一些 `<transition>` 组件,以及许多动画。 我们将在下一节中讨论它!
喜欢这个系列。 演示很棒。
没错。 优秀的系列。
同上!
这是 Vue 作者的一次很棒的演讲