该篇为翻译文章:原文为:Intro to Vue.js: Vuex

该系列文章目录:
Vue.js #1-渲染、指令、事件(原文)

Vue.js #2-组件、属性、Slots(原文)

Vue.js #3-Vue-cli及生命周期(原文)

Vue.js #4-Vuex (当前)

Vue.js #5-动画(原文)

Vuex

上回我们知道了如何从父级组件向子组件传递数据,并且知道了各个兄弟组件之间的数据是不共享的。如果它们之间想要进行一些交互,那么则必须要将数据状态向上传递,然后再向下进行传递。这样做虽然可以达到你要的效果,但一旦你的应用变的复杂之后,就会觉得特别力不从心了。如果你用过Redux的话,那么下面说的一些东西你将会很容易的理解。Vuex就是Vue版本的Redux。当然,你可能知道,Redux也是可以和Vue一起用的,但Vuex是一个更好的选择。

首先,让我们使用npm install vuex或者yarn add vuex进行安装Vuex。

然后,我在/src下面新建一个store目录,里面有一个store.js文件,文件初始化内容如下:

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当中,我们把创建的store添加进来:

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()数据放到store当中,但在这之前 ,我们需要了解以下三个东西:

  • Getters:Getters只是用来读取数据,不能用来改变数据。
  • Mutations:Mutations是用来改变数据状态的,这个改变操作是同步的。它是唯一一种可以改变`store`中数据的方法。
  • Actions:Actions也是用来改变数据状态的,只不过它是异步的,其实它是利用一个已经存在的mutation来完成的。如果你想以一种特定的规则来立即执行一些不同的mutations,那么这个将变的非常有用。

如果你之前没有通过异步的方式来改变数据状态的话,那么你可能有时候对于为什么要使用异步的方式表示难以理解。所以,现在我们首先抽象的了解一下这一块。假如现在有一个满是Gif图片的页面,这些图片很长时间都还没加载完。所以你现在并不想把所有的图片都一起加载,而是一次只加载20张,当页面滚动到最下面的时候再加载另一页。
你可以使用一个mutation来获取下20张图片。但是这个时候你并没有下20张图片的数据。你也不知道什么时候会滚动到页面底部。所以你需要通过事件来监听页面的滚动位置来触发一个动作。
当这个动作触发之后,它会从数据库取到20张图片的信息返回给你。得到这些信息之后,你用mutation包装一下,然后更新视图上的数据即可。
Actions的本质就是创建一个异步请求来请求数据。

一个基本示例

下面的例子向大家展示了在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);
    }
  }
});

上面的代码中可以看到,我们可以在mutations中返回一个数据对象,但这不是一定要返回的,我们只是在需要的时候返回即可。

在组件里面,我们将针对getters使用computed来获取已经计算处理后的值,在methods里面,我们使用dispatch来连接mutationsactions

app.vue:

computed: {
  value() {
    return this.$store.getters.value;
  }
},
methods: {
  increment() {
    this.$store.dispatch('increment', 2)
  }
}

当有许多的mutations/actions时,你可以使用ES6当中的「展开运算符」:

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // map this.increment() to this.$store.commit('increment')
      'decrement',
      'asyncIncrement'
    ])
  }
}

一个真正的实例

再次来看一下我们的「天气预报」APP,我们在这个例子当中加入了Vuex store(点此获取源码)。
在线展示:http://codepen.io/sdras/pen/YNpaoJ

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,即在初始化的时候不显示天气动画。在mutations中,我们切换了showWeather的值。
同时,我们将template初始化为0,我们将使用一个循环的数字(0-3)来代表不同的天气状态,所以在mutations中,我们创建了一个updateTemplate方法。该方法里即更新showWeather的值,还更新template的值(从0到3进行循环)。

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>

这是一个最基本的例子,麻雀虽小,五脏俱全。如果你想更深入的了解Vuex,那么可以查看它的官方文档
另外,例子中用到了<transition>组件以及一些动画。关于动画,我们下回再说!