繁体   English   中英

我怎样才能在 useEffect 钩子体内获取一个新的 state?

[英]How can I grab a fresh state inside a useEffect hook body?

我正在尝试构建一个去抖动钩子。 我以前见过几个实现,但没有一个适合我的需要:通常它们会延迟执行处理程序,直到调用处理程序的尝试停止足够长的时间。

useEffect(() => {
  timeout = setTimeout(handler, 500);

  return () => {
    if (timeout){
      clearTimeout(timeout);    
    }
  }
}, [handler]);

(或类似的东西。)我认为这是有缺陷的,因为如果意图是避免向长时间运行的 function 发送垃圾邮件,它不会考虑 function 是否在超时内返回。 如果在这种情况下获取搜索结果的时间超过 500 毫秒怎么办?

相反,我想尝试运行一个长时间运行的 function(处理程序)。如果没有运行,则执行处理程序并返回其 promise。此外,使用 finally 块检查输入是否已更改,并且如果是这样,再次解雇处理程序。

我想要的用法:

const [input, setInput] = useState<string>("");
const debouncedPromise = useDebounce(() => asyncFunction(input), [input]);

只要输入发生变化,如果处理程序尚未运行,它就可以排队。

这是我写的代码:

import { DependencyList, useEffect, useRef, useState } from "react";

interface IState<T> {
  handler?: () => Promise<T>;
  promise?: Promise<T>;
  isWaiting: boolean;
}

export const useDebounce = <T>(handler: () => Promise<T>, deps: DependencyList = []): Promise<T> | undefined => {
  const [state, setState] = useState<IState<T>>({
    handler,
    isWaiting: false
  });

  const stopWaiting = () => {
    console.log("stopWaiting");
    setState(previousState => ({ ...previousState, waiting: false }));
  };

  const run = () => {
    const promise = handler();
    promise.finally(stopWaiting);

    setState({
      handler,
      isWaiting: true,
      promise,
    });
  };

  useEffect(() => {
    console.log("\nuseEffect");
    console.log(`deps: ${deps}`)
    console.log(`state.isWaiting: ${state.isWaiting}`);
    console.log(`state.handler: ${state.handler}`);
    console.log(`state.promise: ${state.promise}`);

    if (state.isWaiting){
      console.log(">>> state.isWaiting")
      return;
    }
    
    if (handler === state.handler){
      console.log(">>> handler === state.handler")
      return;
    } 
    
    if (state.isWaiting && state.promise && state.handler !== handler){
      console.log(">>> state.isWaiting && state.promise && state.handler !== handler")
      state.promise.finally(run);
      return;
    }

    if (handler !== state.handler){
      console.log(">>> handler !== state.handler")
      run();
    }

    console.log("end useEffect");
  }, [...deps, state.isWaiting]);

  return state.promise;
};

它适用于第一次调用,但它似乎永远不会释放 state.isWaiting 以允许触发子序列、挂起的处理程序:

useEffect UseDebounce.ts:32
deps: T UseDebounce.ts:33
state.isWaiting: false UseDebounce.ts:34
state.handler: function () {
    return asyncFunction(input);
  } UseDebounce.ts:35
state.promise: undefined UseDebounce.ts:36
>>> handler !== state.handler UseDebounce.ts:55
asyncFunction called with T UseDebounce.tsx:9
end useEffect UseDebounce.ts:59

useEffect UseDebounce.ts:32
deps: T UseDebounce.ts:33
state.isWaiting: true UseDebounce.ts:34
state.handler: function () {
    return asyncFunction(input);
  } UseDebounce.ts:35
state.promise: [object Promise] UseDebounce.ts:36
>>> state.isWaiting UseDebounce.ts:39
asyncFunction resolved with T UseDebounce.tsx:12
stopWaiting UseDebounce.ts:16

useEffect UseDebounce.ts:32
deps: Ti UseDebounce.ts:33
state.isWaiting: true UseDebounce.ts:34 // This should be false at this point

我想我被陈旧的 state 困住了。我该如何解决这个问题? 有没有更好的钩子可以用来获得我想要的结果? 一旦输入“稳定下来”,我如何停止触发处理程序?

它似乎永远不会释放state.isWaiting 我想我被陈旧的 state 困住了。

我认为你的问题太新鲜了 state。特别是,你每次都用一个新的、不同的handler调用钩子,所以handler === state.handler可能是真的是初始化 state 的初始值同一个处理程序。 您可能需要在传递的 function 上使用useCallback

但即便如此,你的代码也有问题。 如果在 function 仍在运行时输入多次更改,您将调用state.promise.finally(run); 多次,哪些调度在同一个 promise 上run (实际上是一些陈旧的handler )多次,导致它们同时执行。

有没有更好的钩子可以用来获得我想要的结果?

我不会(仅)使用 state,而是为等待在下一次 function 调用中使用的输入使用一个简单的共享可变引用。 还让run function 自己继续最新的输入,而不是安排一个难以取消的固定处理程序。

所以我会写

interface ResultState<T> {
  running: boolean;
  result?: PromiseSettledResult<T>;
}
function useAsyncEvaluation<T, Args extends DependencyList>(fn: (...args: Args) => Promise<T>, input: Args): ResultState<T> {
  const args = useRef<Args | undefined>();
  const [state, setState] = useState<ResultState<T>>({running: false});
  
  function run() {
    const next = args.current;
    if (!next) return false;
    args.current = undefined;
    fn(...next).then(
      value => ({status: "fulfilled", value}),
      reason => ({status: "rejected", reason})
    ).then(result => {
      setState({running: run(), result});
    );
    return true;
  }
  useEffect(() => {
    args.current = input;
    setState(old => {
      if (old.running) {
        return old;
      } else {
        // calling run() inside the state update prevents race conditions
        return {running: run(), result: old.result};
      }
    });
    return () => {
      args.current = undefined;
    };
  }, input)
  return state;
}

请注意,这会在第一次渲染期间更新效果中的 state并响应input更改,这有点不合需要。 无论如何,您可能实际上并不想这样做——而是从挂钩中返回 function 并从您的事件处理程序中调用它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM