今天是8月30号,这篇文章是7月1号写的
一转眼两个月过去了,7月1号写的文章,写完扔草稿箱了,想着再润色一下,写点注释再发出来。但是后面一直没时间,今天打开掘金一看,都躺草稿箱俩月了,这几天也够累的,现在也没心情写注释了,随便发一下吧。
唉,再说吧
现在是7月1号周二晚上21:30分,瞎忙了一个月的我决定写下一篇文章,但是还没想到标题叫什么。
背景
思路清晰,先说bp(backpround)。背景就是我发现我做的一个小程序,如果不是在主页面的话,会发现页面里的数据接口会比小程序的登录接口早一点执行,这样的话就会导致用户不能直接进到小程序某一个页面,而是要先进入到主页面,然后再自己点进去 。按理说,需要用户登录的情况下,不管在哪个页面,不管在哪一端,第一个执行的接口都应该是登录接口。但是公司的事又比较多,一直都很忙,而且这个问题暂时不会影响业务,所以就一直没处理这个小程序登录的问题。但是,这几天又碰到类似的情况了,这次是一个表单的项目,巨型的表单,这次碰到的情况是每次页面重新加载时都要请求一下接口获取用户的状态信息,而这次,我看着vue3中的onMounted陷入了沉思。
灵感
抄的react的hooks,但是没完全抄,因为也抄不太来。
使用方法和思路
1、先说使用方法,肯定就是使用越简单越好,最好就是像onMounted那样,不需要订阅,不需要解绑,一个钩子函数就可以解决。
像这样
import {onLogin,onGetUserStep} from '@/utils/AppLife';
export default defineComponent({
setup(){
// 用户登录
onLogin(()=>{
console.log('------onLogin-----');
})
// 获取用户状态信息
onGetUserStep(()=>{
console.log('------onGetUserStep-----');
// 获取页面数据
getData();
})
const getData = ()=>{
}
return {}
}
})
2、再说思路,我初始想法就是要有一个单例模式状态机,状态机维护一个用于保存回调函数的数组,在适当的时机遍历这个数组并调用里面的回调函数,然后在app初始的时候去启动这个状态机,状态机就能在适当的时机去遍历并且调用回调函数数组了。
上状态机代码
type LIFE_STATUS = 'init' | string;
type HookFun = () => void;
type CatchFun = (stopPoint:string,reason:any) => void;
class LifecycleListener {
private readonly onComplete:HookFun;
private onError?:CatchFun;
constructor(hook:HookFun){
this.onComplete = hook;
}
public dispatchComplete(){
this.onComplete();
}
public dispatchError(stopPoint:string,reason:any){
this.onError&&this.onError(stopPoint,reason);
}
public catch (fun:CatchFun){
this.onError = fun;
}
}
export interface Stage {
name: string;
method: (params?: any) => Promise<any>;
listeners?: LifecycleListener[];
}
export class AppLoginChain {
private readonly stageArr: Stage[] ;
private stopPoint:string = '';
private hasStop:boolean = false;
private stopReason:any = null;
constructor(stageArr:Stage[]){
stageArr.forEach(stage =>{
stage.listeners = [];
})
this.stageArr = stageArr;
}
private currentStatus: LIFE_STATUS = 'init';
public restart = ()=>{
this.currentStatus = 'init';
this.stopPoint = '';
this.hasStop = false;
this.stopReason = null;
this.stageArr.forEach(stage =>{
stage.listeners = [];
})
return this.initApp();
}
public initApp = async () => {
if(this.currentStatus !== 'init'){
return Promise.resolve();
}
const stageArr = this.stageArr;
// 很难木的住
for(let i = 0; i < stageArr.length; i++){
const stage = stageArr[i];
try {
await stage.method();
this.currentStatus = stage.name;
this.dispatchListenerComplete(stage.listeners!);
stage.listeners!.length = 0;
} catch (error) {
this.hasStop = true;
this.stopReason = error;
this.stopPoint = stage.name;
this.interruptChain(i,error);
break;
}
}
return Promise.resolve();
}
private interruptChain(index:number,error:any){
const stageArr = this.stageArr;
for(let i = index; i < stageArr.length; i++){
const stage = stageArr[i];
this.dispatchListenerError(stage.listeners!,stage.name,error);
stage.listeners!.length = 0;
}
}
private dispatchListenerComplete = (lifecycleHookArr: LifecycleListener[]) => {
lifecycleHookArr.forEach(listener => {
listener.dispatchComplete();
})
}
private dispatchListenerError = (lifecycleHookArr: LifecycleListener[],stopPoint:string,reason:any) => {
lifecycleHookArr.forEach(listener => {
listener.dispatchError(stopPoint,reason);
})
}
private stageIsCompleted = (stageName:string)=>{
const currentIndex = this.findState(this.currentStatus);
const stageIndex = this.findState(stageName);
return currentIndex >= stageIndex;
}
private findState = (name:string):number => {
return this.stageArr.findIndex(e=>{
return e.name === name;
})
}
public registerHooks = (name: string, hook: HookFun) => {
const listener = new LifecycleListener(hook);
const index = this.findState(name);
if (index>-1) {
console.log('this.currentStatus',this.currentStatus);
if (this.stageIsCompleted(name)) {
setTimeout(() => {
hook();
}, 0);
} else {
if(this.hasStop){
setTimeout(() => {
listener.dispatchError(this.stopPoint,this.stopReason);
},0)
}else {
this.stageArr[index].listeners!.push(listener);
}
}
}else{
setTimeout(()=>{
listener.dispatchError(name,name+' not exist.');
},0)
}
return listener;
}
}
状态机的代码很少,构造函数只需要传入任务队列就可以了, 方法也只有几个,暴露三个,一个是启动方法,一个就是注册方法,一个重启方法
接下来我们需要再建一个文件来持有状态机对象和注册对应事件
import Store from "@/store";
import { AppLifeCycle, Stage } from "./AppLifeCycle";
import { Utils } from "@/util/Utils";
import { HttpUtil, API } from "@/http/http";
// 用户登录事件
function userLoginFun() {
return new Promise<void>(async (resolve, reject) => {
let code;
try {
code = await Utils.getWxUserCode();
} catch (error) {
uni.showModal({
title:'登陆错误',
content: "获取wxcode->error:" + error,
});
reject();
return;
}
if (code) {
const params = {
code: code,
};
HttpUtil.request(API.loginWithWechatCode, params).then((res) => {
if (res.code === 0) {
Store.commit("setUser", res.data);
resolve();
} else {
reject();
}
});
}else{
reject();
}
});
}
function getUserNowInfo() {
return new Promise<void>((resolve, reject) =>{
const user = Store.state.user;
if (user.userid) {
HttpUtil.request(API.userNowInfo)
.then(res => {
if (res.code === 0) {
Store.commit('updateUserNowInfo', res.data)
resolve()
} else {
uni.showModal({
title: '登陆错误',
content: `无法获取用户信息${res.msg}`,
showCancel: false
})
reject(res.msg)
}
})
}else{
reject('用户未登录')
}
})
}
// 事件map
const stages: Stage[] = [
{
name: "userLogin",
method: userLoginFun
},
{
name: "getUserInfo",
method: getUserNowInfo
}
]
// 创建状态机对象并暴露出去
export const appLifeCycle = new AppLifeCycle(stages);
// 暴露 onLogin 回调方法
export function onLogin(hook: () => void) {
return appLifeCycle.registerHooks("userLogin", hook);
}
// 暴露 onLogin 回调方法
export function onGetUserInfo(hook: () => void) {
return appLifeCycle.registerHooks("getUserInfo", hook);
}
开始使用
1.在main.ts中
import {appLifeCycle} from '@/util/AppHooks';
appLifeCycle.initApp();
2.在组件中使用
<template>
<div class="col">
aaaaaaaaaaa
</div>
</template>
<script lang="ts">
import { defineComponent,reactive,toRefs,watch,onMounted } from "vue";
import { onLogin,onGetUserStep } from "@/util/AppHooks";
export default defineComponent({
name:'test',
setup(){
onLogin(()=>{
console.log('----onLogin----');
})
onGetUserStep(()=>{
console.log('----onGetUserStep----');
})
return {
};
}
})
</script>
<style scoped>
</style>