- 小程序奇技淫巧之 -- globalDataBehavior管理全局状态
Behaviors 自定义组件中,提供了[代码]behaviors[代码]的使用和定义。 从官方文档我们能看到: [代码]behaviors[代码]是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。 每个[代码]behavior[代码]可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个[代码]behavior[代码]。 简单来说,我们能通过[代码]behaviors[代码]来重构[代码]Component[代码]的能力。Behavior的用处很多,前面也有介绍 computed 计算属性、watch 观察属性的实现,都是使用的 Behavior。 全局状态管理 我们希望全局共享一些数据状态,如果只是通过一个文件的方式进行维护,那么我们无法在状态更新的时候及时地同步到页面。我们需要额外调用 setData 才能更新页面中的 data 数据,才能告诉渲染层这块的数据渲染需要变更,而很多的 Store 状态管理库也是通过这样的方式实现的(事件通知 + setData + 全局状态)。 在小程序 Behavior 能力的支持下,我们可以通过一个全局的 globalData Behavior 注入到每个需要用到的 Component 中,这样就可以在需要的页面中直接引入该 Behavior,就能获取到了。不啰嗦,Behavior的实现如下: [代码]// globalDataStore 用来全局记录 globalData,为了跨页面同步 globalData 用 export let globalDataStore = {}; // 获取本地的 gloabalData 缓存 try { const gloabalData = wx.getStorageSync("gloabalData"); // 有缓存的时候加上 if (gloabalData) { globalDataStore = { ...gloabalData }; } } catch (error) { console.error("gloabalData getStorageSync error", "e =", error); } // globalCount 用来全局记录 setGlobalData 的调用次数,为了在 B 页面回到 A 页面的时候, // 检查页面 __setGlobalDataCount 和 globalCount 是否一致来判断在 B 页面是否有 setGlobalData, // 以此来同步 globalData let globalCount = 0; export default Behavior({ data: { globalData: Object.assign({}, globalDataStore) }, lifetimes: { attached() { // 页面 onLoad 的时候同步一下 globalCount this.__setGlobalDataCount = globalCount; // 同步 globalDataStore 的内容 this.setData({ globalData: Object.assign( {}, this.data.globalData || {}, globalDataStore ) }); } }, pageLifetimes: { show() { // 为了在 B 页面回到 A 页面的时候,检查页面 __setGlobalDataCount 和 globalCount 是否一致来判断在 B 页面是否有 setGlobalData if (this.__setGlobalDataCount != globalCount) { // 同步 globalData this.__setGlobalDataCount = globalCount; this.setGlobalData(Object.assign({}, globalDataStore)); } } }, methods: { // setGlobalData 实现,主要内容为将 globalDataStore 的内容设置进页面的 data 的 globalData 属性中。 setGlobalData(obj: any) { globalCount = globalCount + 1; this.__setGlobalDataCount = this.__setGlobalDataCount + 1; obj = obj || {}; let outObj = Object.keys(obj).reduce((sum, key) => { let _key = "globalData." + key; sum[_key] = obj[key]; return sum; }, {}); this.setData(outObj, () => { globalDataStore = this.data.globalData; }); }, // setGlobalDataAndStorage 实现,先调用 setGlobalData,然后存到 storage 里 setGlobalDataAndStorage(obj: any) { this.setGlobalData(obj); try { let gloabalData = wx.getStorageSync("gloabalData"); // 有缓存的时候加上 if (gloabalData) { gloabalData = { ...gloabalData, ...obj }; } else { gloabalData = { ...obj }; } wx.setStorageSync("gloabalData", gloabalData); } catch (e) { console.error("gloabalData setStorageSync error", "e =", e); } } } }); [代码] 显然,该 Behavior 主要提供了几个能力: 会在小程序 data 添加 globalData 的属性,在 WXML 文件中可以直接通过[代码]{{globalData.xxxx}}[代码]获取到 提供[代码]setGlobalData()[代码]方法,用于更新全局状态 提供[代码]setGlobalDataAndStorage()[代码]方法,用于更新全局状态,同时写入缓存(会在下次启动应用的时候自动获取缓存数据) 这样,我们在初始化 Component 的时候直接引入就可以使用: [代码]Component({ // 在behaviors中引入globalDataBehavior behaviors: [globalDataBehavior], // 其他选项 methods: { test() { // 使用this.setGlobalData可以更新全局的数据状态 this.setGlobalData({ test: "hello world" }); // 使用this.setGlobalDataAndStorage可以更新全局的数据状态,并写入缓存 // 下次globalDataBehavior会默认从缓存中获取 this.setGlobalDataAndStorage({ test: "hello world" }); } } }); [代码] 在引入了 globalDataBehavior 之后,我们的 WXML 就可以直接使用了: [代码]<view>{{ globalData.test }}</view> [代码] 页面如何使用 Behavior [代码]Component[代码]是[代码]Page[代码]的超集,因此可以使用[代码]Component[代码]构造器构造页面。 看看官方文档:事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用[代码]Component[代码]构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应[代码]json[代码]文件中包含[代码]usingComponents[代码]定义段。 更详细的使用方法,在 computed 计算属性、watch 观察属性两篇文章中也有描述,大家可以自行参考。 或者直接查看最终的项目代码:wxapp-typescript-demo。 参考 Component构造器 behaviors 结束语 Behavior 其实是很强大的一个能力,我们能用它来对自己的小程序做很多的能力拓展,缺啥补啥,还可以“混入”给每个 Component 每个方法打入日志,就不用每个组件自己手动打印代码拉。
2019-12-10 - 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23