[英]Debouncing MUI Autocomplete not working properly
我正在嘗試為 mui 自動完成實現debounce
,但效果不佳。
我想在inputValue
更改時向服務器發送去抖請求。
我想念什么嗎?
看起來loadData
觸發了每個輸入更改。 只有第一次加載debounce
有效。
https://codesandbox.io/s/debounce-not-working-j4ixgg?file=/src/App.js
這是沙箱中的代碼:
import { useState, useCallback } from "react";
import { Autocomplete, TextField } from "@mui/material";
import { debounce } from "lodash";
import topFilms from "./topFilms";
export default function App() {
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState("");
const [options, setOptions] = useState([]);
const loadData = () => {
// sleep(1000)
const filteredOptions = topFilms.filter((f) =>
f.title.includes(inputValue)
);
// This log statement added by Ryan Cogswell to show why it isn't working.
console.log(
`loadData with ${filteredOptions.length} options based on "${inputValue}"`
);
setOptions(filteredOptions);
};
const debouncedLoadData = useCallback(debounce(loadData, 1000), []);
const handleInputChange = (e, v) => {
setInputValue(v);
debouncedLoadData();
};
const handleChange = (e, v) => {
setValue(v);
};
return (
<div className="App">
<Autocomplete
value={value}
inputValue={inputValue}
onChange={handleChange}
onInputChange={handleInputChange}
disablePortal
options={options}
getOptionLabel={(option) => option.title}
isOptionEqualToValue={(option, value) => option.title === value.title}
id="combo-box-demo"
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
</div>
);
}
在您的沙箱中, loadData
已成功去抖動,但它始終使用inputValue === ""
執行,然后匹配所有選項。 所有實際過濾隨后都由Autocomplete
組件立即完成,就好像您靜態提供了完整的選項集一樣。 由於 JavaScript 閉包的行為, inputValue
始終為空字符串,因為這是您創建去抖動版本的loadData
時的初始值。
每當創建去抖動函數時,我建議在頂層而不是在組件內部聲明和定義原始 function 和去抖動版本。 您的 debounced function 所依賴的任何局部變量和 state 都可以作為 arguments 傳遞給 function(例如,在我的代碼版本中,輸入值和setOptions
function 傳遞給debouncedLoadData
)。 這避免了由於關閉行為而意外使用過時的值,並消除了對useCallback
的需要。
下面是將loadData
和debouncedLoadData
移動到頂層的代碼的修改版本。 我還更改了loadData
以在傳遞空字符串時不提供任何數據(而不是所有數據),以便更容易看到去抖動行為——否則只要自動完成具有完整的選項集,過濾就會立即發生基於自動完成已有的選項。 這也更接近於模擬真實代碼可能執行的操作(即避免為空字符串調用后端)。
import { useState } from "react";
import { Autocomplete, TextField } from "@mui/material";
import { debounce } from "lodash";
import topFilms from "./topFilms";
const loadData = (inputValue, setOptions) => {
if (inputValue.length === 0) {
console.log("no options");
setOptions([]);
return;
}
const filteredOptions = topFilms.filter((f) =>
f.title.toLowerCase().includes(inputValue.toLowerCase())
);
console.log(
`loadData with ${filteredOptions.length} options based on "${inputValue}"`
);
setOptions(filteredOptions);
};
const debouncedLoadData = debounce(loadData, 1000);
export default function App() {
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState("");
const [options, setOptions] = useState([]);
const handleInputChange = (e, v) => {
setInputValue(v);
debouncedLoadData(v, setOptions);
};
const handleChange = (e, v) => {
setValue(v);
};
return (
<div className="App">
<Autocomplete
value={value}
inputValue={inputValue}
onChange={handleChange}
onInputChange={handleInputChange}
disablePortal
options={options}
getOptionLabel={(option) => option.title}
isOptionEqualToValue={(option, value) => option.title === value.title}
id="combo-box-demo"
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
</div>
);
}
你可以通過幾個鈎子一起工作來完成這個。 首先讓我們看一下用於去抖動 state 的鈎子:
function useDebounce(value, delay, initialValue) {
const [state, setState] = useState(initialValue);
useEffect(() => {
console.log("delaying", value);
const timer = setTimeout(() => setState(value), delay);
// clear timeout should the value change while already debouncing
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return state;
}
上面的代碼接受一個值並稍后從鈎子返回去抖值。 useEffect 末尾的回調阻止了一堆計時器一個接一個地觸發。
然后您的組件可以減少為:
export default function App() {
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState("");
const [options, setOptions] = useState([]);
const debouncedValue = useDebounce(inputValue, 1000);
// fetch data from server
useEffect(() => {
console.log("fetching", debouncedValue);
const filteredOptions = topFilms.filter((f) =>
f.title.includes(debouncedValue)
);
setOptions(filteredOptions);
}, [debouncedValue]);
const handleInputChange = (e, v) => {
setInputValue(v);
};
const handleChange = (e, v) => {
setValue(v);
};
return (
<div className="App">
<Autocomplete
// props
/>
</div>
);
}
這里的useEffect是在依賴debouncedValue
通過react state業務改變時運行的。 在 TextField 中鍵入時,您的控制台應如下所示:
delaying s
delaying sp
delaying spi
fetching spi
delaying spir
fetching spir
App
中的這個useEffect
為您提供了一個很好的位置,可以像您提到的那樣獲取到服務器。 useEffect
可以很容易地替換為useQuery 之類的東西
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.