ahooks 源码解读系列 - 2

1,401 阅读2分钟

这个系列是将 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;

以上内容由于本人水平问题难免有误,欢迎大家进行讨论反馈。