将React挂钩与可观察对象一起使用时,如何维护最新的回调值?

发布时间:2020-07-07 17:26

我正在使用Rx.js编写自己的Preact / React反跳钩,如下所示:

export type Debouncer<T> = (value: T) => void;
export function useDebounceCallback<T>(callback: Debouncer<T>, timeout: number) {

  const subject = useSubject<T>();
  const ref = useRef(callback);

  // Is this ugly?
  ref.current = callback;

  useEffect(() => {

    const sub = subject
      .pipe(debounceTime(timeout))
      .subscribe((value) => {
        ref.current(value);
      });

    return () => sub.unsubscribe();

  }, []);

  return (value: T) => subject.next(value);
}

上面的代码依赖于callback函数,当observable产生新值时调用该函数,因此应成为useEffect依赖项的一部分。但这引起了一个问题,因为可观察对象只应订阅一次。每次渲染周期结束时,callback都是新创建的。而且,在用户级别上,无法保证callback会被包裹在useCallback挂钩中。

始终将最新的callback值传递给可观察的订阅功能的正确方法是什么?

我正在考虑如上所述使用useRef,然后在每个渲染周期立即用ref.current值更新callback,但我担心,我也可能违反钩子定律!

回答1

这里有几个简单的想法-也许其他人可以提供更深入的答案。

首先,据我所知,在ref.callback内使用useEffect不会违反钩子定律,因为与callback不同,ref彼此相同渲染。因此,通常来说,您的方法对我来说似乎不错,除了timeout参数似乎确实违反了法律(但可以通过将timeout传递给useEffect来轻松解决)。

第二,我自己在这种情况下(不确定是否适合您)尝试使用的一种方法是创建一个返回钩子的函数,而不是创建一个钩子:

const getUseDebounceCallback = <T extends unknown>(
  callback: (value: T) => void,
  timeout: number,
) => {
  const subject = new Subject<T>();
  return () => { <the body of the hook> };
};

通过这种方式,您可以在挂钩中的任何位置使用callbacktimeout,而不会违反任何规则。这是一种非常干净的方法,但是这意味着该钩子的用户需要编写一个返回组件的函数,而不是编写组件:

const getComponent = (...) => {
  const useDebounceCallback = getUseDebounceCallback(...)
  return (props: ...) => { <component body that uses useDebounceCallback> }
}

这在某些情况下确实很好用,我很好奇它是否可以在您的情况下使用。