全局变量的实现方式

我们这里说的全局变量,着重指的是能够全局动态响应的情况。

说到全局变量,我们首先想到的可能就是vuex,确实,这是最好的实现方式。在uni-app台,我们还可以有其他的实现方式,这里我们做一个抛砖引玉的讨论,当然,我们 推荐的,还是使用uView封装后的vuex的实现方式,它具有配置简单,使用方便的特点。

整体来说,在uni-app台,可以有如下实现全局变量的方式:

  1. 本地存储
  2. 配置文件
  3. 挂载Vue.prototype
  4. globalData
  5. Vuex

下面分别对这几个实现方式进行讲解,由于1到4点由于各种原因,与我们要讨论的全局动态响应不是很切合,实现起来也较简单,所以不着重讨论。

本地存储

这是一种持久存储的方式,类似于web中的Local Storage,当我们需要将一个变量保存很长一段时间,比如用户的登录状态(Token),才会使用这种方式, 同时,频繁对这种方式进行存和取,是有性能消耗的,应用生命周期(应用从启动到关闭)内使用的变量,不应该使用此方式操作。

此种存储方式,有同步和异步之分:

  • 同步的意思是,下一步的操作要等获取存储的内容之后才能进行,一般获取变量的时候,都使用此种方式。
  1. // 同步存储
  2. uni.setStorageSync('key', 'value');
  3. // 同步获取
  4. let key = uni.getStorageSync('key');
  5. // 这一句要等从本地存储中获取了key之后,才执行
  6. let val = 1;
  • 异步的意思是,执行取或存的过程中,先执行后面的代码,而另一边在回调中得到存储的结果。
  1. // 异步存储
  2. uni.setStorage({
  3. key: 'key',
  4. data: 'value',
  5. success: function () {
  6. // 存储成功的回调
  7. }
  8. });
  9. // 下面这行会比存储成功的回调先执行
  10. let val = 1;
  11. // 异步获取
  12. uni.getStorage({
  13. key: 'key',
  14. success: function (res) {
  15. // 获取成功的回调
  16. }
  17. });
  18. // 下面这行会比获取成功的回调先执行
  19. let val = 1;

配置文件

配置文件,顾名思义,就是把一些变量写入到js文件中,再通过export default的形式导出,一般什么情况会使用这种方式呢,是我们要从用户尚未开始安装 APP之前,直到用户卸载APP,都需要存在的这样一些变量或者配置。比如我们可以把向后端请求的域名写到配置文件中,其他情况不适用这种存储变量的方式。

  1. // config.js,根目录的common文件夹中
  2. export default {
  3. domain: 'http://www.example.com',
  4. }

需要使用的时候,我们通过import引入即可,这种方式,缺点是每次都需要引入文件,我们无法将挂载在到Vue.prototype上。

  1. import config from "./uview-ui/libs/config/config.js"
  2. export default {
  3. onLoad() {
  4. console.log(config.domain);
  5. }
  6. }

我们可以在main.js中将从config.js中获取的值挂载到Vue.prototype,再在页面通过this.xxx形式获取。

注意: 这种挂载的方式,在微信小程序中,无法在模板中直接读取xxx值(结果为undefined),只能在js中读取(HX 2.6.11,当前最新稳定版)。

  1. // main.js
  2. import config from "./uview-ui/libs/config/config.js"
  3. Vue.prototype.domain = config.domain;
  1. // demo.vue
  2. <template>
  3. <!-- 微信小程序中值为undefined,其他端有效 -->
  4. <view>
  5. 值为:{{this.domain}}
  6. </view>
  7. </template>
  8. <script>
  9. export default {
  10. onLoad() {
  11. console.log(this.domain)
  12. }
  13. }
  14. </script>

挂载到Vue.prototype

使用挂载到Vue.prototype的方式,需要在根目录的main.js中进行,在页面中,我们可以使用this.xxx的形式获取变量,注意上面说的,在微信小程序模板 无法读取挂在的值,只能在js中使用。

具体使用,见上方的配置文件中的介绍

globalData

这个方式,最早是微信小程序特有的,它无法使用vuex进行全局状态的管理,就造了这个方式。

可能您会问,为什么uni-app了vuex还要有这个呢?

globalData是微信小程序的特性,uni-app对微信小程序的另一个实现,顺理成章的就有了globalData,另外的原因也是因为globalData使用简单,也有它存在的理由。 当然,globalData也不是动态响应的,也就是说,您在A.vue修改了globalData中的某个值username,在B.vue中对这个值的引用是无法自动更新的,vuex却是可以做到的。

由上,因为无法自动更新,为了做到这一点,所以我们需要在页面的onShow生命周期中获取globalData的值,或许您会问,为什么一定是onShow呢,onLoad不行吗? onLoad获取值是没问题的,但是当我们从A.vue进入B.vue中(假设A和B页面都通过globalData引用了某个userName),在B.vue中修改了globalData的 userName,当我们返回A.vue页面时,onLoad不会再次触发,但是onShow就如它的字面意思,是会再次触发的,所以我们需要把对globalData的获取放在onShow生命周期。

下面为使用globalData的示范:

  1. 对globalData的定义,需要在App.vue中进行
  1. // App.vue
  2. export default {
  3. globalData: {
  4. userName: '白居易'
  5. },
  6. // 这里需要注意的是,如果我们需要在App.vue中使用userName
  7. // 使用getApp().globalData.userName是不行,因为此时getApp()尚未生成
  8. // 1. 非V3模式,可以通过this.$scope.globalData获取
  9. // 2. V3模式,可以通过getApp({allowDefault: true}).globalData获取
  10. // 详见uni-app文档:https://uniapp.dcloud.io/collocation/frame/window?id=getapp
  11. onLaunch() {
  12. console.log(this.$scope.globalData.userName);
  13. }
  14. }
  1. 定义好了globalData,我们进入A.vue,并使用userName
  1. <!-- A.vue -->
  2. <template>
  3. <view>
  4. <!-- 注意,不能在模板中直接使用 getApp().globalData.userName -->
  5. <<琵琶行>>的作者是:{{author}}
  6. </view>
  7. </template>
  8. <script>
  9. export default {
  10. data() {
  11. return {
  12. author: ''
  13. }
  14. },
  15. onShow() {
  16. // 每次A.vue出现在屏幕上时,都会触发onShow,从而更新author值
  17. this.author = getApp().globalData.userName;
  18. }
  19. }
  20. </script>
  1. 当我们从A.vue进入B.vue时,引用并修改userName的值
  1. <!-- B.vue -->
  2. <template>
  3. <view>
  4. <view>
  5. <!-- 注意,不能在模板中直接使用 getApp().globalData.userName -->
  6. <<卖炭翁>>的作者是:{{author}}
  7. </view>
  8. <view>
  9. <u-button @click="modifyUserName">修改userName值</u-button>
  10. </view>
  11. </view>
  12. </template>
  13. <script>
  14. export default {
  15. data() {
  16. return {
  17. author: ''
  18. }
  19. },
  20. onShow() {
  21. // 每次B.vue出现在屏幕上时,都会触发onShow,从而更新author值
  22. this.author = getApp().globalData.userName;
  23. },
  24. methods: {
  25. modifyUserName() {
  26. getApp().globalData.userName = "诗圣"
  27. // 修改userName后,本页的author依然不会自动刷新,因为globalData不是响应式的
  28. // 我们仍然需要手动刷新本页的author值,由此可见globalData的弊端
  29. this.author = getApp().globalData.userName;
  30. }
  31. }
  32. }
  33. </script>
  1. 假设我们从B.vue返回A.vue,这时A.vue出现在屏幕上,触发了它的onShow生命周期,执行了this.author = getApp().globalData.userName;, 因而我们可以看到A.vue的值由白居易变成了在B.vue中修改后的诗圣

Vuex的实现方式

我们希望您使用vuex方式,但是也希望您对其他的实现方式有所了解,知己知彼,才能闲庭信步,了然于胸,所以把vuex的讲解放在最后。

这里介绍两个写法,一是传统的写法,类似于web中对vuex的使用,这里默认大家都会,只是举例,不进行过多解读。二是uView进行一定封装优化后的写法, 新项目可以使用这个形式,它具体有简单易用的特点,当然它不是全能的,您依然可以补充自己的内容进去,它和传统的写法是不冲突的。

(一) 传统实现方式

  1. 在uni-app目根目录新建store文件夹,并在其中创建index.js,内容如下:

为了避免和页面data变量混淆,可以给state中的变量添加一个特定的前缀,比如”vuex_“

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. const store = new Vuex.Store({
  5. state: {
  6. vuex_token: '123654789'
  7. },
  8. mutations: {
  9. // payload为用户传递的值,可以是单一值或者对象
  10. modifyToken(state, payload) {
  11. state.vuex_token = payload.token;
  12. }
  13. }
  14. })
  15. export default store
  1. 在uni-app目根目录的main.js中,引入vuex
  1. // main.js
  2. import store from '@/store';
  3. // 将store放入Vue对象创建中
  4. const app = new Vue({
  5. store,
  6. ...App
  7. })
  1. demo.vue页面中使用并修改state中的vuex_token变量
  1. <template>
  2. <view>
  3. <view>
  4. Token值为{{vuex_token}}
  5. </view>
  6. <u-button @click="btnClick">修改vuex_token</u-button>
  7. </view>
  8. </template>
  9. <script>
  10. import {mapState, mapMutations} from 'vuex';
  11. export default {
  12. computed: {
  13. ...mapState(['vuex_token'])
  14. },
  15. methods: {
  16. ...mapMutations(['modifyToken']),
  17. btnClick() {
  18. // 这里第二个参数可以普通变量或者对象,自定义的,根据mutations需求处理
  19. this.$store.commit('modifyToken', {token: 'xxxyyyzzz'})
  20. }
  21. }
  22. }
  23. </script>

(二) uView进行一定改进后写法

上面(一)中介绍的传统写法简单,但是也很繁琐:

  1. 我们需要在vuex中定义statemutations
  2. 我们需要在每个用到vuex变量的地方,都引入mapState,同时还要解构到computed中去
  3. 修改vuex变量的时候,还需要通过commit提交
  4. 由于vuex变量是保存在运行内存中的,H5中刷新浏览器vuex变量会消失,还需要通过其他手段实现变量的存续

我们相信大家都会上面的用法,也肯定会想,有没有更简单的做法呢,答案是肯定的,uView专门对这个问题进行了优化,解决您一般情况下的苦恼。 这个实现的方式,不是万能的,如果您需要自己的逻辑,可以融入传统的写法,是不冲突的。

说明:确保您是新项目的情况下,或者您对这个实现方法很清楚,才使用这个方法。

我们把实现的基本原理放到了另一个独立的专题,如果您感兴趣,可以点击这里查看:uView优化Vuex的写法的基本原理

具体实现

  1. uni-app目根目录新建’/store/index.js’,并复制如下内容到其中

注意:如果某个变量需要保存到APP的下一次启动中,或者需要H5刷新之后不消失,在state中声明后,还需要写入到saveStateKeys数组中, 同时,在state中也需要写上lifeData.xxx ? lifeData.xxx : yyy的形式,保证应用启动时能把从存储中获取的值赋值给变量,见如下:

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. let lifeData = {};
  5. try{
  6. // 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的
  7. lifeData = uni.getStorageSync('lifeData');
  8. }catch(e){
  9. }
  10. // 需要永久存储,且下次APP启动需要取出的,在state中的变量名
  11. let saveStateKeys = ['vuex_user', 'vuex_token'];
  12. // 保存变量到本地存储中
  13. const saveLifeData = function(key, value){
  14. // 判断变量名是否在需要存储的数组中
  15. if(saveStateKeys.indexOf(key) != -1) {
  16. // 获取本地存储的lifeData对象,将变量添加到对象中
  17. let tmp = uni.getStorageSync('lifeData');
  18. // 第一次打开APP,不存在lifeData变量,故放一个{}空对象
  19. tmp = tmp ? tmp : {};
  20. tmp[key] = value;
  21. // 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中
  22. uni.setStorageSync('lifeData', tmp);
  23. }
  24. }
  25. const store = new Vuex.Store({
  26. // 下面这些值仅为示例,使用过程中请删除
  27. state: {
  28. // 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量
  29. // 加上vuex_前缀,是防止变量名冲突,也让人一目了然
  30. vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {name: '明月'},
  31. vuex_token: lifeData.vuex_token ? lifeData.vuex_token : '',
  32. // 如果vuex_version无需保存到本地永久存储,无需lifeData.vuex_version方式
  33. vuex_version: '1.0.1',
  34. },
  35. mutations: {
  36. $uStore(state, payload) {
  37. // 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1
  38. let nameArr = payload.name.split('.');
  39. let saveKey = '';
  40. let len = nameArr.length;
  41. if(nameArr.length >= 2) {
  42. let obj = state[nameArr[0]];
  43. for(let i = 1; i < len - 1; i ++) {
  44. obj = obj[nameArr[i]];
  45. }
  46. obj[nameArr[len - 1]] = payload.value;
  47. saveKey = nameArr[0];
  48. } else {
  49. // 单层级变量,在state就是一个普通变量的情况
  50. state[payload.name] = payload.value;
  51. saveKey = payload.name;
  52. }
  53. // 保存变量到本地,见顶部函数定义
  54. saveLifeData(saveKey, state[saveKey])
  55. }
  56. }
  57. })
  58. export default store
  1. uni-app目根目录新建’/store/$u.mixin.js’,并复制如下内容到其中,由于HX某些版本的限制,我们无法帮您自动引入”$u.mixin.js”,您需要在main.js 中手动引入,并mixin处理。

以下为”main.js”文件:

  1. // main.js
  2. let vuexStore = require("@/store/$u.mixin.js");
  3. Vue.mixin(vuexStore);

以下为”$u.mixin.js”文件:

  1. // $u.mixin.js
  2. import { mapState } from 'vuex'
  3. import store from "@/store"
  4. // 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中
  5. let $uStoreKey = [];
  6. try{
  7. $uStoreKey = store.state ? Object.keys(store.state) : [];
  8. }catch(e){
  9. }
  10. module.exports = {
  11. created() {
  12. // 将vuex方法挂在到$u中
  13. // 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗')
  14. // 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1')
  15. this.$u.vuex = (name, value) => {
  16. this.$store.commit('$uStore', {
  17. name,value
  18. })
  19. }
  20. },
  21. computed: {
  22. // 将vuex的state中的所有变量,解构到全局混入的mixin中
  23. ...mapState($uStoreKey)
  24. }
  25. }
  1. 在项目根目录的main.js中,引入”/store/index.js”,并放到Vue示例中
  1. // main.js
  2. import store from '@/store';
  3. // 将store放入Vue对象创建中
  4. const app = new Vue({
  5. store,
  6. ...App
  7. })
  1. 在页面使用vuex变量

假设我们在vuexstate中定义了vuex_version变量和vuex_user对象

  1. state: {
  2. vuex_version: '1.0.0',
  3. vuex_user: {
  4. name: '白居易'
  5. }
  6. }

demo.vue页面使用和修改这些变量,他们是动态全局响应的。

这里用的修改方式为:this.$u.vuex(key, value):

  1. 如果要修改state中的vuex_version变量为1.0.3,则:this.$u.vuex(‘vuex_version’, ‘1.0.3’)。
  2. 如果要修改state中的vuex_user对象的name属性为青柠,则:this.$u.vuex(‘vuex_user.name’, ‘青柠’),与1中不同的是,对象的话, 需要用点”.”分隔开。
  1. <!-- demo.vue -->
  2. <template>
  3. <view>
  4. <view>
  5. 版本号为:{{vuex_version}}
  6. </view>
  7. <view>
  8. <<琵琶行>>的作者为{{vuex_user.name}}
  9. </view>
  10. <u-button @click="modifyVuex">修改变量</u-button>
  11. </view>
  12. </template>
  13. <script>
  14. export default {
  15. methods: {
  16. modifyVuex() {
  17. this.$u.vuex('vuex_version', '1.0.1');
  18. // 修改对象的形式,中间用"."分隔
  19. this.$u.vuex('vuex_user.name', '诗圣');
  20. }
  21. }
  22. }
  23. </script>