簡體   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