这个系列是将 ahooks 里面的所有 hook 源码都进行解读,通过解读 ahooks 的源码来熟悉自定义 hook 的写法,提高自己写自定义 hook 的能力,希望能够对大家有所帮助。
往期回顾
useEventEmitter
hook 版的 event bus,用于多组件通讯。
import { useRef, useEffect } from 'react';
type Subscription<T> = (val: T) => void;
/// 超简版的事件管理器
/// 使用 set 作为容器,注册的时候并没有记录事件类型,所以触发的时候只会全部触发
export class EventEmitter<T> {
private subscriptions = new Set<Subscription<T>>();
emit = (val: T) => {
for (const subscription of this.subscriptions) {
subscription(val);
}
};
useSubscription = (callback: Subscription<T>) => {
/// ahooks 中有大量的这种代码,意思是在运行时永远使用最新的 cb
/// 同时也不会因为 cb 的更新重复执行后面的逻辑
const callbackRef = useRef<Subscription<T>>();
callbackRef.current = callback;
useEffect(() => {
/// 由于新建了 subscription 函数来执行 cb,所以即使是相同的 cb 也是会被重复注册的
function subscription(val: T) {
if (callbackRef.current) {
callbackRef.current(val);
}
}
this.subscriptions.add(subscription);
return () => {
this.subscriptions.delete(subscription);
};
}, []);
};
}
export default function useEventEmitter<T = void>() {
const ref = useRef<EventEmitter<T>>();
if (!ref.current) {
ref.current = new EventEmitter();
}
return ref.current;
}
useLockFn
异步锁,只是锁传入的 fn,无法锁住生成的 cb
import { useRef, useCallback } from 'react';
function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) {
/// 是否在异步请求中的标识,如果已经在请求中则直接拒绝执行 fn
const lockRef = useRef(false);
return useCallback(
async (...args: P) => {
/// 注意此处,直接return相当于当前匿名函数返回了 Promise.resolve(undefined)
/// 举个例子:const submit = useLockFn(fn);
/// [1,2,3].forEach(n => submit().then(fn2))
/// 此时 fn 只会执行1次,但是 fn2 会执行3次,顺序为 2 3 1
/// 所以应该在 fn 中处理完所有后续逻辑,不能在拿到的 cb 里面去处理
if (lockRef.current) return;
lockRef.current = true;
try {
const ret = await fn(...args);
lockRef.current = false;
return ret;
} catch (e) {
lockRef.current = false;
throw e;
}
},
[fn],
);
}
export default useLockFn;
usePersisFn
得到一个引用永远不会改变的“稳如老狗”的函数,如何从 useCallback 读取一个经常变化的值?的一种抽象实现。
import { useRef } from 'react';
export type noop = (...args: any[]) => any;
function usePersistFn<T extends noop>(fn: T) {
const fnRef = useRef<T>(fn);
fnRef.current = fn;
/// 保证返回的函数引用永远不会改变
const persistFn = useRef<T>();
if (!persistFn.current) {
/// 返回的永远是这个匿名函数,所以引用不会改变
persistFn.current = function (...args) {
return fnRef.current!.apply(this, args);
} as T;
}
return persistFn.current!;
}
export default usePersistFn;
以上内容由于本人水平问题难免有误,欢迎大家进行讨论反馈。