泰罗小程序登录能力赋能
发表时间:2021-4-30
发布人:葵宇科技
浏览次数:57
问题
不是为了设计为设计,最初的设计是为了解决核心的四个问题,实现与登录有关的逻辑收紧,与具体页面或者组件解耦。
设想
基于登录有关的逻辑进行抽离,进行统一设计。
以下涉及代码的部分,都在小程序项目中Demo分支中,其中hoc的组件我们认为还是组件,所以在components中新建立了hoc的文件夹,而hook从官方定义来说,不一定是组件,更多的期望是可复用逻辑,所以目录在于components并列新建了hooks文件夹。针对测试用的页面建立在test文件夹。
设计思路
赋能页面
页面拦截器
大致我们可以思考到以下方向以及方案。
由于其他方式暂时不可用,目前我们采用跳转方法增加拦截的方式实现的需求,但这种在直接跳转的页面是无法实现拦截的,针对这种页面,我们需要使用针对页面组件在页面内增加相关的登录赋能,实现登录状态的监听。
//路由跳转拦截器
(function taroRouterIntercept(){
const {navigateTo,redirectTo}=Taro;
const noTokenWhiteList=['/login'];//没有token能进的页面 除去tab根页面,因为已经做适配
const noLoginWhiteList=noTokenWhiteList.concat(['/diary','/user']);//没有token能进的页面 除去tab根页面,因为已经做适配
function tokenIntercept(url){
return new Promise((resolve,reject)=>{
if(!UserManager.getToken())//进行token效验
{
const needToken=!(noTokenWhiteList.some((val)=>{
return url.startsWith(val);
}));
if(needToken)//需要token
{
UserManager.initToken().then(()=>{
resolve();
}).catch(()=>{
reject();
Taro.showToast({ title: '授权失败', icon: 'none' })//APP拒绝登录
})
}else{
resolve();
}
}else{
resolve();
}
})
}
function loginIntercept(url){
return new Promise((resolve,reject)=>{
if(!UserManager.isLogin())
{
console.log('noLoginWhiteList',noLoginWhiteList)
const needLogin=!(noLoginWhiteList.some((val)=>{
return url.startsWith(val);
}));
if(needLogin)//需要登录
{
reject();
UserManager.jumpToLogin();//这里之后可以改造成登录之后继续做跳转
}else{
resolve();
}
}else{
resolve();
}
})
}
Taro.navigateTo=function(params){
const url=params.url;
tokenIntercept(url).then(()=>{
loginIntercept(url).then(()=>{
navigateTo(params);//拦截都过的时候再执行跳转
})
});
}
Taro.redirectTo=function(params){
const url=params.url;
tokenIntercept(url).then(()=>{
loginIntercept(url).then(()=>{
redirectTo(params);//拦截都过的时候再执行跳转
})
});
}
})();
复制代码
后续的计划,我们会提供中间件的写法,让方法重写、中间件加入更加优雅。
针对类组件的页面
提供hoc的写法,用反继承的方式为组件提供类似生命周期的钩子函数,并提供注入isLogin的state状态。 hoc的写法 :
import Taro, {
Component,
} from "@tarojs/taro";
import UserManager from "../../utils/UserManager";
export default function withLoginStatus(WrappedComponent) {
// ...并返回另一个组件...
return class extends WrappedComponent {//不这么写子组件生命周期不会执行 且渲染有问题
hx_componentWillMount(){
//这样写后面能立即获取到已经塞入isLogin值的state
this.setState({
isLogin: !!UserManager.isLogin()
})
super.hx_componentWillMount();
}
componentDidMount() {
// 订阅登录登出动作
Taro.eventCenter.on('login',this.loginStatusChange.bind(this,true))
Taro.eventCenter.on('logout', this.loginStatusChange.bind(this, false))
super.componentDidMount();
}
/**
*
* @param {bool} isLogin 登录状态
*/
loginStatusChange = (isLogin) => {
this.setState({isLogin});
super.loginStatusChange && super.loginStatusChange(isLogin);
}
componentWillUnmount() {
Taro.eventCenter.off('login',this.loginStatusChange);
Taro.eventCenter.off('logout', this.loginStatusChange);
super.componentWillUnmount();
}
};
}
复制代码
后续计划: 1 是否登录状态能否写入props中
其使用方式 :基本达到预期效果,对原代码的侵入性很小。
import Taro, {
Component
} from "@tarojs/taro";
import ContainView from "../../../components/ContainView";
import withLoginStatus from "../../../components/HOCS/withLoginStatus";
@withLoginStatus
class Test extends Component {
state = {
data:1,
}
loginStatusChange(isLogin) {
console.log('进行数据更新')
if (isLogin) {
this.setState(state => {
data : state.data ++
} )
}
}
render() {
const {isLogin } = this.state
return<ContainView>
<View>
{isLogin ? '需要登录' : `刷新之后的数据:${data}`}
</View>
</ContainView>
}
}
export default Test
复制代码
针对函数组件的页面
针对函数组件,我们提供了两种方式,先介绍第一种,在提供hook,并借助useEffect的情况下 。
hook的源码:
import Taro, { useState } from '@tarojs/taro'
import UserManager from "../utils/UserManager";
/**
* @description 增加对事件的订阅 其他事情不做
*/
function useLoginStatus(){
const [isLogin, setIsLogin] = useState(!!UserManager.isLogin())
function bindChange(flag) {
setIsLogin(flag)
}
Taro.eventCenter.on('login',bindChange)
Taro.eventCenter.on('logout', bindChange)
function offWatchLogin() {
console.log('停止监听')
Taro.eventCenter.off('login',bindChange)
Taro.eventCenter.off('logout',bindChange)
}
return [isLogin,setIsLogin,offWatchLogin]
}
export default useLoginStatus
复制代码
函数组件的使用方式:
注意事项: 1 增加对获取到的isLogin的 useEffect的监听,执行需要执行的函数 2 在组件销毁时,执行取消监听函数 offWatchLogin
import Taro, {
Component,useEffect,useState,useDidHide
} from "@tarojs/taro";
import ContainView from "../../../components/ContainView";
import withLoginStatus from "../../../hooks/withLoginStatus ";
function Demo() {
const [isLogin,setIsLogin,offWatchLogin] = withLoginStatus();
const [hasGet,setHasGet]= useState(false)
const [data, setData] = useState(0);
function getData() {
setData(data+1 )
}
useEffect(() => {
getData();
},[])
// 增加一个useEffect 判断是否需要更新
useEffect(() => {
console.log(3333)
console.log('change',isLogin)
if (isLogin && !hasGet) {
console.log('需要更新数据')
getData()
setHasGet(true)
} else {
setHasGet(false)
}
}, [isLogin]);
function leave() {
Taro.navigateTo({
url:'/topic/pages/index/index'
})
}
useDidHide(() => {
console.log('销毁执行');
offWatchLogin()
})
return <ContainView>{!isLogin ? '需要登录' : `刷新之后的数据:${data}`}
<View onClick={leave}>离开测试</View>
</ContainView>
}
export default Demo
复制代码
赋能组件
针对类组件
与上面赋能页面组件相同,不再赘述
针对函数组件
与上面赋能页面组件相同,不再赘述。这里重点介绍下可能会变化的设计细节: 1 需要导出的非数组,而是对象,那么可用性更加灵活 2 针对一些 其他的针对登录可以抽取的逻辑,可使用hook的方式实现,然后针对本来要执行的函数,使用高级函数的方式包裹。比如判断没有登录,需要先跳转登录;如果已经登录,直接执行函数。 3 使用非useEffect的方式,而是提供绑定函数的方式
另外一种hook的写法,代码如下:
import Taro, { useState } from '@tarojs/taro'
import UserManager from "../utils/UserManager";
/**
* @description 增加对事件的订阅 其他事情不做
*/
function useLoginStatus(){
const [isLogin, setIsLogin] = useState(!!UserManager.isLogin());
let callBack = null;
function bindChange(flag) {
setIsLogin(flag)
if (callBack && typeof callBack === 'function') {
callBack(flag)
}
}
function initWatchLogin() {
Taro.eventCenter.on('login',()=>bindChange(true))
Taro.eventCenter.on('logout', ()=>bindChange(false))
}
initWatchLogin()
const mockLogin = setInterval(() => {
const flag = Math.random() - 0.5 > 0 ? 'login' : 'logout';
console.log(flag)
Taro.eventCenter.trigger(flag)
console.log(111111)
}, 4000)
// const mockLogin1 = setTimeout(() => {
// Taro.eventCenter.trigger('logout')
// }, 2000)
function offWatchLogin() {
console.log('停止监听')
Taro.eventCenter.off('login',bindChange)
Taro.eventCenter.off('logout',bindChange)
}
// 追加绑定事件
function loginStatusChangeBind(cb) {
callBack =cb
}
useDidHide(() => {
console.log('销毁执行');
offWatchLogin()
})
function judgeLogin(cb,...rest) {
// 没有登录的情况下跳转登录
console.log('current', isLogin)
if (!isLogin) {
Taro.navigateTo({
url:'/login/pages/login/index'
})
}
return cb(rest)
}
return { isLogin, setIsLogin, offWatchLogin,loginStatusChangeBind,judgeLogin }
}
export default useLoginStatus
复制代码
函数式组件内的使用:
import Taro, {
Component,useState
} from "@tarojs/taro";
import withLoginStatus from "../../../hooks/withLoginStatusObj";
/**
*
* @param {*} props
*/
function DemoButton(props) {
const { } = props;
const [text,setText] = useState('未登录')
const { isLogin,offWatchLogin, loginStatusChangeBind,judgeLogin } = withLoginStatus();
loginStatusChangeBind(dataChangeByFlag)
/**
* @description 根据登录状态需要执行的函数
* @param {*} flag
*/
function dataChangeByFlag(flag){
setText(`${!!!flag ? '未':''}登录`)
}
function test(args) {
console.log('执行功能',args)
}
return <View style={{ border: '1px solid blue' }} onClick={(args)=>judgeLogin(test,args,1) }>
权限按钮({text})
</View>
}
export default DemoButton
复制代码
小结
本文主要介绍与登录赋能有关的教程以及基本使用,除了页面拦截方法已经在使用,其他方法还未具体实践,有任何问题欢迎随时反馈。
本文中涉及到的一些设计思想和使用方式可以大家可以更大程度的发散,以后遇到其他类似场景能够举一反三。