簡體   English   中英

去抖動 MUI 自動完成功能無法正常工作

[英]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的需要。

下面是將loadDatadebouncedLoadData移動到頂層的代碼的修改版本。 我還更改了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>
  );
}

編輯去抖工作

tldr:codesandbox 鏈接

你可以通過幾個鈎子一起工作來完成這個。 首先讓我們看一下用於去抖動 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM