一个行为的生命周期
在前端应用中,行为就意味着一次人机交互。在大多数业务场景中,我们都需要对用户操作前、后进行额外的逻辑处理。并且一个操作中会存在状态的流转,操作前->进行时->结束。这些内容都需要我们用代码去书写。
所以,本次使用react来实现一个通用的用户行为记录hooks。
「levle 1」用户行为钩子
在用户交互时,核心逻辑执行前后,我们很多时候需要进行额外的操作。比如执行前校验,执行后埋点上报等等。
useActionsLifeCycle
通过上面的描述,我们知道这个行为存在两个生命周期,before & after
type ActionsLifeCycleCommonFn<T extends any[],R> = (...args:T) => Promise<R>;
const useActionsLifeCycle= <T extends any[], R>(data: {
before?: ActionsLifeCycleCommonFn<T,R>;
after?: (...args: T) => void;
}) => {
const { before, after } = data;
return useCallback(
(fn: ActionsLifeCycleCommonFn<T,R>) =>
async (...args: T) => {
before && (await before(...args));
await fn(...args);
after?.(...args);
},
[before, after]
);
};
测试一下~
一些问题?
可以看到我们很多行为是需要一些时间处理的,然而上面的代码并没有对用户的连点进行额外的处理。
「level 2」行为,在同一时间有且只有一个
「level 1」的问题是用户频繁操作时,会反复执行该动作。大多数业务场景中,是不允许这样操作滴,比如在表单提交中,用户狂点提交按钮,可能会导致创建多个或是其他问题。并且不进行阻止的话,对后端服务器的压力也会增大的哟。毕竟可能会多很多无效请求嘛
阻止用户反复点击,我们通常使用防抖、节流的方式去做。但在这样的场景下并不能完全解决频次的问题,因为一个固定的时间间隔,并不是真实的事件结束。
所以!
useExclusiveAsyncOperation
使用缓存promise的方法,解决时间间隔不准的问题~
type AsyncFunction<T> = (...args: any) => Promise<T>;
const exclusiveAsyncOperation = function <T>(func: AsyncFunction<T>) {
let activePromise: Promise<T> | null = null;
return async (...args: any) => {
if (activePromise) {
return activePromise;
}
activePromise = func(...args);
try {
return await activePromise;
} catch (error) {
activePromise = null;
return error;
} finally {
activePromise = null;
}
};
};
函数版本
type AsyncFunction<T> = (...args: any) => Promise<T>;
const exclusiveAsyncOperation = function <T>(func: AsyncFunction<T>) {
let activePromise: Promise<T> | null = null;
return async (...args: any) => {
if (activePromise) {
return activePromise;
}
activePromise = func(...args);
try {
return await activePromise;
} catch (error) {
activePromise = null;
return error;
} finally {
activePromise = null;
}
};
};
测试一下~
可以看到,用户再疯狂点击,也不会导致反复执行啦~
还可以再进一步吗?
当然啦!现在我们点击按钮之后,再次点击,按钮一点反应都没有!!
用户的操作,需要有回响!
「level 3」 行为状态
用户在「操作A」未完成时,继续执行「下一个操作」,在「level 2」中我们已经完成了阻止。
但是并没有给用户的「下一个」操作回应。所以开发者需要知道「操作A」的执行状态,并把这个状态反馈给用户。
usePromseStatus
我们知道promise中有
pending
,fulfilled
,rejected
三个状态,我们可以通过promise的状态,来判断该行为的进行状态。
enum PromiseStatus{
unStart='unStart',
pending='pending',
fulfilled='fulfilled',
rejected='rejected',
}
const usePromseStatus = <T extends any[],R>(promise: (...args:T) => Promise<R>) => {
const [state, setState] = useState<PromiseStatus>(PromiseStatus.unStart);
const newPromise = useCallback(async (...args:T) => {
try{
setState(PromiseStatus.pending);
const res = await promise(...args)
setState(PromiseStatus.fulfilled);
return res;
}catch(e){
setState(PromiseStatus.rejected)
return e;
}
}, [promise])
return [newPromise, state]
}
测试一下~
总结
通过「level 1-3」的hooks,我们完成了一次对用户行为的通用钩子函数的封装。
使用
useActionsLifeCycle
监听行为发生前后使用
exclusiveAsyncOperation
阻止「操作A」未结束前的多次调用使用
usePromseStatus
完成对行为状态的监控,实时的反馈给用户当然我们这次是三个函数分别使用的,实际上把三个结合在一起封装成一个新的hooks,就好啦!
这里留一个坑~为什么最后不继续封装成一个综合hooks呢?
因为以上的每一个hooks封装都使用了高阶函数,使得函数的层级越来越深。层数越深的代码,对于开发的理解难度就越高。
目前还没有想好怎么把层级拍平,既能保证单个hooks的可用,也能组合成一个层级不那么深的高阶hooks~
原文链接:https://juejin.cn/post/7326923332196122663 作者:小气小憩