背景
小程序中,有些页面需要先登录,拿到token再调用其他接口,实现页面逻辑。一般做法是在onLoad里面判断是否登录,没有登录就跳转登录页面,已经登录就执行逻辑,onShow里面也是没有登录就不执行逻辑。登录返回来以后,不会再走onLoad,就需要再onShow里面调用接口,这样会造成几个问题
- 如果第一次进来就是登录了的,onLoad和nShow都会调用接口,造成多次调用
- 如果onLoad里面不调用,只做登录判断,onShow里面调用,确实可以避免进到页面多次调用的问题,但是每次onShow都会调用,有时我们希望在只在onLoad时调用一次
拦截实现思路
我们希望在需要登录的页面的onLoad和onShow执行之前,先执行我们的登录逻辑,登录完成后再执行页面的onLoad和onShow
- 收集要监听的方法和回调
- 重写Page的onLoad和onShow,在重写的onLoad里面先执行回调函数,再依次执行原始的onLoad和onShow
实现
interceptor.js
type callBackFun = () => Promise<any>;
export default class interceptor {
interceptorMaps: Map<string, callBackFun> // 要拦截的函数和回调
constructor() {
this.interceptorMaps = new Map()
}
on(key: string, callBack:callBackFun) {
this.interceptorMaps.set(key, callBack)
return this
}
apply() {
const self = this
const originPage = Page // 存放原始的Page
Page = function (option) { // 重写Page,当小程序的Page执行的时候,相当于执行了我们重写的Page
const originOload = option.onLoad
const originOshow = option.onShow
option.onLoad = function (opt) {
const callBack = self.interceptorMaps.get('onLoad')
if (!callBack || !option.needLogin) { // 没有回调函数或者不需要登录
originOload && originOload.call(this,opt)
return
}
// 需要登录且有回调函数
new Promise(async (resolve, reject) => {
this.__proto__.execute = true // 正在执行onLoad的异步函数,防止onShow提前执行
const res = await callBack().catch(err => {
reject(err)
})
resolve(res)
}).then(() => {
if (originOload) {
originOload.call(this,opt)
originOshow?.call(this)
this.__proto__.execute = false // onLoad的异步函数执行完成,需重置,防止返回页面不重新执行onShow
}
}).catch(err => {
console.log('登录失败', err)
})
}
option.onShow = function () {
if (!this.__proto__.execute) {
originOshow?.call(this)
}
}
originPage(option) // 需要手动执行原始的Page,防止原始方法丢失
}
return this
}
}
使用
在app.ts里面
import interceptor from './interceptor'
const interceptorFun = new interceptor()
interceptorFun.on('onLoad', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true) // 登录成功
// reject('登录失败')
}, 6000)
})
}).apply()
注意
在需要登录的页面page里面配置needLogin:true
Page({
needLogin: true
})
扩展
这段代码和设计模式的关系
- 装饰器模式:在这段代码中,类
interceptor通过on方法向interceptorMaps中注册了要拦截的函数和对应的回调函数。然后在apply方法中,对Page函数进行了重写,增加了额外的功能,这部分类似于装饰器模式的作用。 - 代理模式:在
apply方法中,对Page函数进行了代理,通过重写onLoad和onShow方法,实现了对原始函数的代理,并在代理中加入了额外的逻辑。