在快应用开发中使用 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-vuex";

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 自动更新,不再需要借助父组件。由于代码和运行结果与父子组件传值类似,就不再赘述了。

参考文档

Tags

vivo developer

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