[英]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.