在快应用开发中使用 Vuex

快应用教程 May 21, 2020

什么是“状态管理模式”?

我们先来看一段官方代码:

new Vue({ 
    // state 数据源 
    data () { 
        return { 
            count: 0 
        }
    }, 
    // view 视图
    template: `
        <div>{{ count }}</div>
    `, 
    // actions 事件 
    methods: { 
        increment () { 
            this.count++ 
        } 
    } 
})

这是一个很简单的增长型计数功能页面,通过事件 increment,实现 count 增长,然后渲染到界面上去。官方称作为「单向数据流」。

单向数据流

但是,当情况发生改变,例如现在有两个页面 A 和 B,并且还有以下两个要求:

  • 要求它们都能对 count 进行操控。
  • 要求 A 修改了 count 后,B 要第一时间知道,B 修改后,A 也要第一时间知道。

很容易想到,如果我们把数据源 count 剥离开来,用一个全局变量或者全局单例的模式进行管理,这样在任何页面都可以很容易的取到这个状态了,这就是 Vuex 背后的思想。

但是 vuex 和单纯的全局对象还是有区别的,官方的描述如下:

Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

核心概念

Vuex 主要由五部分组成:State、Getter、Mutation、Action 和 Module,本次主要介绍前四个部分以及一些辅助函数在快应用中的使用:

  1. State vuex 使用 state 保存应用中使用到的所有状态,由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态

  2. Getter 有时候,我们会发现 State 中的数据,并不是我们直接想要的,而是需要经过相应的处理后,才能满足我们的需求,这时就可以使用 Getter 对 State 中的数据进行处理后再返回。Getter 也可以在计算属性中返回。

  3. Mutation 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串类型的事件类型 (type) 和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。注意,我们不能直接调用 mutation,Vuex 规定必须使用store.commit来触发对应 type 的方法。同时,使用 store.commit 时可以传递额外参数,官方称为「载荷」。最后,还有一点非常重要,Mutation 的回调函数必须是同步函数

  4. Action Action 类似于 mutation,但是有几点不同:第一:Action 使用 dispatch 触发;第二:Action 提交的是 mutation,而不是直接变更状态;第三:Action 可以包含任意异步操作

  5. 辅助函数 辅助函数包括 mapState、mapGetters、mapMutations、mapActions,辅助函数能够帮助我们更优雅地使用 Vuex,例如:如果一个组件需要使用到多个状态,我们可能会写出如下的代码:

    export default {
      ...
      computed: {
          a () {
            return store.state.a
          },
          b () {
            return store.state.b
          },
          c () {
            return store.state.c
          },
          ...
       }
    }
    

    这种写法当然是没问题的,但是总感觉不太简洁,此时,我们就可以借助辅助函数:

    import { mapState } from "vuex";
    
    export default {
      // ...
      computed: {
        ...mapState([
          // 映射 this.a 为 store.state.a
          "a",
          "b",
          "c"
        ])
      }
    };
    

    借助 mapState,代码简洁了许多。同样的,其他几个辅助函数的使用方式也是类似的。

简单示例

要在快应用中使用 vuex 首先肯定要安装 vuex,但是由于快应用和 vue 的实现是有差异的,不能直接使用 vuex,需要对 vuex 的源码做一些处理。这里我们直接使用大佬的 quickapp-vuex,使用方式与 vuex 基本相同

npm install quickapp-vuex -S //npm安装

之后,我们就可以在项目中使用 vuex 了。首先,新建一个 store.js 文件:

import Vuex from "quickapp-vuex";

export default new Vuex.Store({
  state: {
    count: 1
  },
  getters: {
    count(state) {
      return "getter" + state.count;
    }
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    }
  }
});

之后,在 app.ux 中引入 store.js,并挂到全局对象上

import store from "path to store.js";
import Vuex from "quickapp-vue";

Vuex.install(store);

之后我们就能在组件和页面中使用 vuex 了。注意,与在 vue 中使用 vuex 不同,在快应用中使用 vuex 需要包一层Vuex.Component,其他的和 vuex 用法类似

<template>
  <div class="demo-page">
    <text class="title">欢迎打开{{count}}</text>
    <text class="btn" onclick="increment">+</text>
    <text class="btn" onclick="decrement">-</text>
  </div>
</template>

<script>
import {mapGetters, mapMutations, Component} from 'quickapp-vuex'

export default Component({
  computed:{
    ...mapGetters(['count'])
  },

  ...mapMutations(['increment', 'decrement'])
})
</script>

使用效果如下图:

在快应用中使用vuex示例

实际使用--父子组件传值

当父子组件需要依赖同一个状态值时,若只有一个子组件,则可以使用 props 和发消息的方式(已经有点繁琐);如果,子组件又有子组件,子组件的子组件又有子组件(开始套娃)......若还是使用 props 和发消息的方式,复杂度将呈指数级增长。但是,借助 vuex,则能很优雅地处理这种组件层层嵌套的情况。

还是使用简单示例中计数器的例子,首先在父组件中引入子组件

//index.ux
<import name="test1" src="./test1.ux"></import>

<template>
  <div class="demo-page">
    <text class="title">父组件{{ count }}</text>
    <div>
      <text class="btn" onclick="increment">+</text>
      <text class="btn" onclick="decrement">-</text>
    </div>
    <test1></test1>
  </div>
</template>

<script>
import { mapGetters, mapMutations, Component } from "quickapp-vuex";

export default Component({
  computed: {
    ...mapGetters(["count"])
  },

  ...mapMutations(["increment", "decrement"])
});
</script>

然后在子组件中再引入子组件的子组件

//test1.ux
<import name="test2" src="./test2.ux"></import>

<template>
  <div class="demo-page">
    <text class="title">子组件{{ count }}</text>
    <div>
      <text class="btn" onclick="increment">+</text>
      <text class="btn" onclick="decrement">-</text>
    </div>
    <test2></test2>
  </div>
</template>

<script>
import { mapGetters, mapMutations, Component } from "quickapp-vuex";

export default Component({
  computed: {
    ...mapGetters(["count"])
  },

  ...mapMutations(["increment", "decrement"])
});
</script>
//test2.ux
<template>
  <div class="demo-page">
    <text class="title">子组件的子组件{{ count }}</text>
    <div>
      <text class="btn" onclick="increment">+</text>
      <text class="btn" onclick="decrement">-</text>
    </div>
  </div>
</template>

<script>
import { mapGetters, mapMutations, Component } from "quickapp-vuex";

export default Component({
  computed: {
    ...mapGetters(["count"])
  },

  ...mapMutations(["increment", "decrement"])
});
</script>

基本上是几段差不多的代码的嵌套,但是已经能够说明问题,运行效果如下图:

在快应用中使用vuex实现父子组件传值

可以看到,当点击一个层级的组件的按钮时,其他层级的组件的状态也相应发生改变,达到了我们想要的效果。

实际使用--兄弟组件传值

以往在快应用中,若涉及到兄弟组件之间传值,则需要借助父组件。一个子组件先将信息传给父组件,然后父组件再将信息传给另一个子组件,非常麻烦。借助 vuex,我们只需要将两个兄弟组件依赖同一个 state,则当其中一个组件修改 state 时,另一个组件的 state 自动更新,不再需要借助父组件。由于代码和运行结果与父子组件传值类似,就不再赘述了。

参考文档

vivo developer

快应用引擎、工具开发者、快应用生态拓展达人(vivo)。

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.