Vue.js 简介:Vuex

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您的旅程每个阶段提供云产品。 立即开始使用 $200 免费积分!

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

文章系列

  1. 渲染、指令和事件
  2. 组件、道具和插槽
  3. Vue-cli
  4. Vuex (您现在身处这里!)
  5. 动画

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>` 组件,以及许多动画。 我们将在下一节中讨论它!

文章系列

  1. 渲染、指令和事件
  2. 组件、道具和插槽
  3. Vue-cli
  4. Vuex (您现在身处这里!)
  5. 动画