uniapp全局变量的实现
这里说全局变量,着重指的是能够全局动态响应的情况。说到全局变量,我们首先想到的可能就是
vuex
,确实,这是最好的实现方式。在uni-app中,我们还可以有其他的实现方式,当然,我们推荐的,还是使用uView封装后的vuex
的实现方式,它具有配置简单,使用方便的特点。
参考文档:
vue中实现全局变量:https://www.cnblogs.com/kewenxin/p/8619240.html
微信小程序中实现全局变量:https://www.jianshu.com/p/11e377cc6157
整体来说,在uni-app中,可以有如下实现全局变量的方式:
- 本地存储
- 配置文件
- 挂载Vue.prototype
- globalData
- Vuex
#本地存储
这是一种持久存储的方式,类似于web
中的Local Storage
,当我们需要将一个变量保存很长一段时间,比如用户的登录状(Token),才会使用这种方式, 同时,频繁对这种方式进行存和取,较耗性能的,应用生命周期(应用从启动到关闭)内使用的变量,不应该使用此方式操作。
此种存储方式,有同步和异步之分:
- 同步,下一步的操作要等获取存储的内容之后才能进行,一般获取变量的时候,都使用此种方式。
// 同步存储uni.setStorageSync('key', 'value');// 同步获取let key = uni.getStorageSync('key');// 等从本地存储中获取了key之后,才执行let val = 1;
- 异步,执行取或存的过程中,先执行后面的代码,而另一边在回调中得到存储的结果。
// 异步存储uni.setStorage({ key: 'key', data: 'value', success: function () { // 存储成功的回调 }});// 下面这行会比存储成功的回调先执行let val = 1;// 异步获取uni.getStorage({ key: 'key', success: function (res) { // 获取成功的回调 }});// 下面这行会比获取成功的回调先执行let val = 1;
#文件配置
顾名思义,就是把一些变量写入到js
文件中,再通过export default
的形式导出,一般什么情况会使用这种方式呢,是我们要从用户尚未开始安装 APP之前,直到用户卸载APP,都需要存在的这样一些变量或者配置。比如我们可以把向后端请求的域名写到配置文件中,其他情况不适用这种存储变量的方式。
// config.js,根目录的common文件夹中export default {domain: 'http://www.example.com',}
需要使用的时候,我们通过import
引入即可,这种方式,缺点是每次都需要引入文件,我们无法将挂载在到Vue.prototype上。
import config from "./uview-ui/libs/config/config.js"export default {onLoad() {console.log(config.domain);}}
我们可以在main.js
中将从config.js
中获取的值挂载到Vue.prototype,再在页面通过this.xxx
形式获取。
注意: 这种挂载的方式,微信小程序中,无法在模板中直接读取xxx
值(undefined),只能在js中读取(HX 2.6.11,当前较稳版)。
// main.jsimport config from "./uview-ui/libs/config/config.js"Vue.prototype.domain = config.domain;
// demo.vue值为:{{this.domain}}export default {onLoad() {console.log(this.domain)}}
#挂载到Vue.prototype
使用挂载到Vue.prototype的方式,需要在根目录的main.js
中全局进行,在页面中,我们可以使用this.xxx
的形式获取变量,注意上面说的,在微信小程序模板 无法读取挂在的值,只能在js中使用。
具体使用,见上方的文件配置
中的介绍
#globalData
这个方式,最早是微信小程序特有的,它无法使用vuex
进行全局状态的管理,就造了这个方式。小程序有globalData,这是一种简单的全局变量机制。这套机制在uni-app里也可以使用,App.vue 可以定义 globalData ,也可以使用 API 读写这个值。并且全端通用。
可能您会问,为什么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
生命周期。
js中操作globalData的方式如下: getApp().globalData.text = 'test'
在应用onLaunch时,getApp对象还未获取,暂时可以使用this.$scope.globalData获取globalData。
如果需要把globalData的数据绑定到页面上,可在页面的onShow页面生命周期里进行变量重赋值。
nvue的weex编译模式中使用globalData的话,由于weex生命周期不支持onShow,但熟悉5+的话,可利用监听webview的addEventListener show事件实现onShow效果,或者直接使用weex生命周期中的beforeCreate。但建议开发者使用uni-app编译模式,而不是weex编译模式。
globalData是简单的全局变量,如果使用状态管理,请使用vuex
(main.js中定义)
下面使用globalData的示范:
1.对globalData的定义,需要在App.vue中进行
// App.vueexport default {globalData: { db_picture: 'https://testimg.xxxxx.com/', uploadUrl: 'https://test.xxxxx.com',userName: '白居易'},// 这里需要注意的是,如果我们需要在App.vue中使用userName// 使用getApp().globalData.userName是不行,因为此时getApp()尚未生成// 1. 非V3模式,可以通过this.$scope.globalData获取// 2. V3模式,可以通过getApp({allowDefault: true}).globalData获取// 详见uni-app文档:https://uniapp.dcloud.io/collocation/frame/window?id=getapponLaunch() {console.log(this.$scope.globalData.userName);}}
2.定义好了globalData,我们进入A.vue
,并使用userName
值
<>的作者是:{{author}}export default {data() {return {author: ''}},onShow() {// 每次A.vue出现在屏幕上时,都会触发onShow,从而更新author值this.author = getApp().globalData.userName;}}
3.当我们从A.vue
进入B.vue
时,引用并修改userName
的值
<>的作者是:{{author}}修改userName值export default {data() {return {author: ''}},onShow() {// 每次B.vue出现在屏幕上时,都会触发onShow,从而更新author值this.author = getApp().globalData.userName;},methods: {modifyUserName() {getApp().globalData.userName = "诗圣";// 修改userName后,本页的author依然不会自动刷新,因为globalData不是响应式的// 我们仍然需要手动刷新本页的author值,由此可见globalData的弊端this.author = getApp().globalData.userName;}}}
4.假设我们从B.vue
返回A.vue
,这时A.vue
出现在屏幕上,触发了它的onShow
生命周期,执行了this.author = getApp().globalData.userName;
, 因而我们可以看到A.vue
的值由白居易
变成了在B.vue
中修改后的诗圣
。
PS:觉得还有更好的处理方式,开发研究中!(日后更新)
#Vuex的实现方式
我们希望使用vuex
方式,但是也希望您对其他的实现方式有所了解,知己知彼,才能闲庭信步,了然于胸。
这里介绍两个写法,一是传统的写法,类似于web
中对vuex
的使用。二是uView进行一定封装优化后的写法, 新项目可以使用这个形式,它具体有简单易用的特点,当然它不是全能的,按需求添加,它和传统的写法是不冲突的。
(一) 传统实现方式
1、在uni-app目根目录新建store
文件夹,并在其中创建index.js
,内容如下:为了避免和页面data
变量混淆,可以给state
中的变量添加一个特定的前缀,比如"vuex_"
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({state: {vuex_token: '123654789'},mutations: {// payload为用户传递的值,可以是单一值或者对象modifyToken(state, payload) {state.vuex_token = payload.token;}}})export default store
2、在uni-app目根目录的main.js
中,引入vuex
// main.jsimport store from '@/store';// 将store放入Vue对象创建中const app = new Vue({store,...App})
3、在demo.vue
页面中使用并修改state
中的vuex_token
变量
Token值为{{vuex_token}}修改vuex_tokenimport {mapState, mapMutations} from 'vuex'; export default {computed: { ...mapState(['vuex_token']) }, methods: { ...mapMutations(['modifyToken']),btnClick() {// 这里第二个参数可以普通变量或者对象,自定义的,根据mutations需求处理this.$store.commit('modifyToken', {token: 'xxxyyyzzz'})}} }
(二) uView进行一定改进后写法
上面(一)中介绍的传统写法简单,但是也很繁琐:
- 我们需要在
vuex
中定义state
和mutations
- 我们需要在每个用到
vuex
变量的地方,都引入mapState
,同时还要解构到computed
中去 - 修改
vuex
变量的时候,还需要通过commit
提交 - 由于
vuex
变量是保存在运行内存中的,H5中刷新浏览器vuex
变量会消失,还需要通过其他手段实现变量的存续
我们相信大家都会上面的用法,也肯定会想,有没有更简单的做法呢,答案是肯定的,uView专门对这个问题进行了优化,解决您一般情况下的苦恼。 这个实现的方式,不是万能的,如果您需要自己的逻辑,可以融入传统的写法,是不冲突的。
说明:确保您是新项目的情况下,或者您对这个实现方法很清楚,才使用这个方法。
具体实现
1、uni-app目根目录新建'/store/index.js',并复制如下内容到其中
注意:如果某个变量需要保存到APP的下一次启动中,或者需要H5刷新之后不消失,在state
中声明后,还需要写入到saveStateKeys
数组中, 同时,在state
中也需要写上lifeData.xxx ? lifeData.xxx : yyy
的形式,保证应用启动时能把从存储中获取的值赋值给变量,见如下:
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)let lifeData = {};try{// 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的lifeData = uni.getStorageSync('lifeData');}catch(e){}// 需要永久存储,且下次APP启动需要取出的,在state中的变量名let saveStateKeys = ['vuex_user', 'vuex_token'];// 保存变量到本地存储中const saveLifeData = function(key, value){// 判断变量名是否在需要存储的数组中if(saveStateKeys.indexOf(key) != -1) {// 获取本地存储的lifeData对象,将变量添加到对象中let tmp = uni.getStorageSync('lifeData');// 第一次打开APP,不存在lifeData变量,故放一个{}空对象tmp = tmp ? tmp : {};tmp[key] = value;// 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中uni.setStorageSync('lifeData', tmp);}}const store = new Vuex.Store({// 下面这些值仅为示例,使用过程中请删除state: {// 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量// 加上vuex_前缀,是防止变量名冲突,也让人一目了然vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {name: '明月'},vuex_token: lifeData.vuex_token ? lifeData.vuex_token : '',// 如果vuex_version无需保存到本地永久存储,无需lifeData.vuex_version方式vuex_version: '1.0.1',},mutations: {$uStore(state, payload) {// 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1let nameArr = payload.name.split('.');let saveKey = '';let len = nameArr.length;if(nameArr.length >= 2) {let obj = state[nameArr[0]];for(let i = 1; i < len - 1; i ++) {obj = obj[nameArr[i]];}obj[nameArr[len - 1]] = payload.value;saveKey = nameArr[0];} else {// 单层级变量,在state就是一个普通变量的情况state[payload.name] = payload.value;saveKey = payload.name;}// 保存变量到本地,见顶部函数定义saveLifeData(saveKey, state[saveKey])}}})export default store
2、uni-app目根目录新建'/store/$u.mixin.js',并复制如下内容到其中,由于HX某些版本的限制,我们无法帮您自动引入"$u.mixin.js",您需要在main.js
中手动引入,并mixin处理。
// main.jslet vuexStore = require("@/store/$u.mixin.js");Vue.mixin(vuexStore);
// $u.mixin.jsimport { mapState } from 'vuex'import store from "@/store"// 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中let $uStoreKey = [];try{$uStoreKey = store.state ? Object.keys(store.state) : [];}catch(e){}module.exports = {created() {// 将vuex方法挂在到$u中// 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗')// 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1')this.$u.vuex = (name, value) => {this.$store.commit('$uStore', {name,value})}},computed: {// 将vuex的state中的所有变量,解构到全局混入的mixin中...mapState($uStoreKey)}}
3、在项目根目录的main.js
中,引入"/store/index.js",并放到Vue示例中
// main.jsimport store from '@/store';// 将store放入Vue对象创建中const app = new Vue({store,...App})
4、在页面使用vuex
变量,假设我们在vuex
的state
中定义了vuex_version
变量和vuex_user
对象
state: {vuex_version: '1.0.0',vuex_user: {name: '白居易'}}
在demo.vue
页面使用和修改这些变量,他们是动态全局响应的。
这里用的修改方式为:this.$u.vuex(key, value):
- 如果要修改
state
中的vuex_version
变量为1.0.3
,则:this.$u.vuex('vuex_version', '1.0.3')。 - 如果要修改
state
中的vuex_user
对象的name
属性为小二,则:this.$u.vuex('vuex_user.name', '小二'),与1中不同的是,对象的话, 需要用点"."分隔开。
版本号为:{{vuex_version}}<>的作者为{{vuex_user.name}}修改变量export default {methods: {modifyVuex() {this.$u.vuex('vuex_version', '1.0.1');// 修改对象的形式,中间用"."分隔this.$u.vuex('vuex_user.name', '诗圣');}}}
#uView优化Vuex
vuex
传统写法简单,但是也很繁琐:
- 我们需要在
vuex
中定义state
和mutations
- 我们需要在每个用到
vuex
变量的地方,都引入mapState
,同时还要解构到computed
中去 - 修改
vuex
变量的时候,还需要通过commit
提交 - 由于
vuex
变量是保存在运行内存中的,刷新浏览器vuex
变量会消失,还需要通过其他手段实现变量的存续
针对上面的第1点,我们写了一个统一的mutations
,用于更新所有的state
变量,这样可以免去每个变量都要写一个对应的mutations
步骤,同时在 这个统一的mutations
中,根据配置判断是否在变量更新的时候,把它存进本地Local Storage
,这样H5刷新或者APP下次启动,就会自动把这些变量赋值给 state
具体实现如下(当然,这可能不是最优的写法)
// 根目录的/store/index.js$uStore(state, payload) {// 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1let nameArr = payload.name.split('.');let saveKey = '';let len = nameArr.length;if(nameArr.length >= 2) {let obj = state[nameArr[0]];for(let i = 1; i < len - 1; i ++) {obj = obj[nameArr[i]];}obj[nameArr[len - 1]] = payload.value;saveKey = nameArr[0];} else {// 单层级变量,在state就是一个普通变量的情况state[payload.name] = payload.value;saveKey = payload.name;}saveLifeData(saveKey, state[saveKey])}
针对上面第二点,我们通过Vue.mixin
全局混入的形式,可以很好的解决。mixin
会把内容注入到每一个页面,所以我们在其中写了mapState
到computed
, 每个页面自然地就获得了从vuex
的state
中注入的全局变量,这里我们需要在uni-app目根目录新建一个store
文件夹(如果没有的话), 在其中新建一个$u.mixin.js
文件,这个文件无需您手动引入和Vue.mixin
处理,uView会自动处理,只为让您少写两行代码。
// $u.mixin.js的部分实现import { mapState } from 'vuex'import store from "@/store"// 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中let $uStoreKey = [];try{$uStoreKey = store.state ? Object.keys(store.state) : [];}catch(e){}module.exports = {computed: {// 将vuex的state中的所有变量,解构到全局混入的mixin中...mapState($uStoreKey)}}
针对上面的第3点,因为我们通过统一的mutations
的去更新state
,自然就需要一个统一方法去触发mutations
,用以替代原来的commit
方法, 具体是将此方法顺带写入到$u.mixin.js
,并挂载到this.$u
中,命名为vuex
,见如下:
module.exports = {created() {// 将vuex方法挂在到$u中// 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗')// 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1')this.$u.vuex = (name, value) => {this.$store.commit('$uStore', {name,value})}}}
当我们要修改某一个state
值的时候,就用this.$u.vuex('name', value)
的形式,通过其他办法,也可以实现this.name = value
的简写,但是这种方式 不支持对象的修改,同时也会造成其他的问题,这里不做过多讨论。
针对第4点,其实已经在第1点中解决了。
上面的做法,只是抛砖引玉的做了一个思路的解析,如果您有更好的思路,也可以和我们分享。
说明:确保您是新项目的情况下,或者您对这个实现方法很清楚,才使用这个方法。