reactjs 去抖MUI自动完成无法正常工作

dohp0rv5  于 2023-01-04  发布在  React
关注(0)|答案(2)|浏览(152)
    • bounty将在5天后过期**。回答此问题可获得+50声望奖励。ton1希望引起更多人关注此问题。

我正在尝试为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>
  );
}
idfiyjo8

idfiyjo81#

在您的沙箱中,loadData成功地去抖,但是它总是使用inputValue === ""执行,然后匹配所有选项。所有实际的过滤都立即由Autocomplete组件完成,就像您静态提供了全套选项一样。由于JavaScript闭包的行为,inputValue始终为空字符串,因为这是创建loadData去抖版本时的初始值。
无论何时创建去抖函数,我建议在顶层声明和定义原始函数和去抖版本,而不是在组件内部。去抖函数所依赖的任何局部变量和状态都可以作为参数传递给函数(例如,在我的代码版本中,将输入值和X1 M5 N1 X函数传递到X1 M6 N1 X)。这避免了由于闭包行为而意外使用过时的值,并消除了对useCallback的需要。
下面是代码的修改版本,它将loadDatadebouncedLoadData移到顶层。(而不是所有数据),以便更容易看到去抖动行为--否则,每当自动完成具有完整的选项集时,过滤将基于自动完成已经有的选项立即发生。2这也更接近真实的代码可能做的事情(即避免为空字符串调用后端)。

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>
  );
}

f8rj6qna

f8rj6qna2#

tldr: codesandbox link
你可以通过几个钩子一起工作来完成这个任务。首先让我们看看去抖动状态的钩子:

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>
  );
}

当依赖项debouncedValue通过react state business更改时,此处的useEffect将运行。当键入TextField时,控制台应如下所示:

delaying s
delaying sp
delaying spi
fetching spi
delaying spir
fetching spir

App中的useEffect为您提供了一个很好的位置来执行对服务器的提取,就像您提到的那样。

相关问题