为什么我的 React 去抖处理程序从未被调用过?

Why is my React debounce handler never called?

我正在使用 React 16.13.0 和 lodash。我被推荐使用 debounce 作为一种在用户输入搜索词时将搜索请求正确发送到我的服务器的方法。我实现了这个...

...

const handleChange = (event, searchTerm, setSearchTerm, setSearchResults) => {
  console.log("search term:" + searchTerm);
  const query = event.target.value;
  setSearchTerm(query);
  if (!query) {
    setSearchResults( [] );
  } else {
    doSearch(query, searchTerm, setSearchResults);
  }
}

const getDebouncedHandler = (e, handler, delay) => {
  console.log("value:" + e.target.value);
  _.debounce(handler, delay);
}
...

const Search = (props) => {
  const [searchTerm, setSearchTerm] = useState('');
  const [searchResults, setSearchResults] = useState([]);

  const renderSearchResults = ...

  return (
    <div className="searchForm">
      <input
        type="text"
        placeholder="Search"
        value={searchTerm}
        onChange={(e) => {getDebouncedHandler(e, (e) => {handleChange(e, searchTerm, setSearchTerm, setSearchResults); }, 100)}}
      />
      {renderSearchResults()}
    </div>
  );
}

export default Search;

问题是,虽然我看到我的“getDebouncedHandler”方法被调用,但我不认为

_.debounce(handler, delay);

正在做任何事情,因为我从未看到那里列出的方法被调用。我还需要做什么才能正确调用去抖动处理程序?

编辑: 添加我用于“doSearch”的逻辑

const doSearch = (query, searchTerm, setSearchResults) => {
  console.log("before fetch, with query:" + query);
  const searchUrl = "/coops/?contains=" + encodeURIComponent(query);
  fetch(searchUrl, {
    method: "GET",
  })
    .then((response) => response.json())
    .then((data) => {
      console.log("returning data for " + searchTerm + " query:" + query);
      console.log(data);
      if (query === searchTerm) {
        console.log("setting search results for search term:" + searchTerm);
        setSearchResults(data);
      }
    });
}

_.debounce(handler, delay) returns 函数 - 您必须使用该函数作为事件处理程序。

所以创建一个函数:

const debouncedHandleChange = _.debounce(handleChange, delay)

在 jsx 中:

onChange = {e => debouncedHandleChange(e, searchTerm, setSearchTerm, setSearchResults)}

您必须只对搜索结果的获取进行去抖动,而不是对输入进行去抖动,即如果您对 searchTerm 的设置进行去抖动,则在用户停止输入之前不会设置 searchTerm。因此,在他停止输入之前,他输入的任何内容都不会显示。因此,相应地更改代码,看看它是否解决了您的问题。

这里发生了几件事。

  1. lodash.debounce方法returns一个函数。然后你必须调用它。
  2. 您要传递的事件是合成的,不会保留供以后使用,因此最好立即传递您需要的 (event.target.value)。

因此您的代码将如下所示;

import React, {useState} from "react";
import _ from 'lodash';

const handleChange = (value, searchTerm, setSearchTerm, setSearchResults) => {
  // expect a value here instead of an event
  console.log("search term:" + searchTerm);
  const query = value;
  setSearchTerm(query);
  if (!query) {
    console.log('no search');
  } else {
    console.log('do search');
  }
}

const getDebouncedHandler = (e, handler, delay) => {
  console.log("value : " + e.target.value, handler);
  _.debounce(handler, delay)(e.target.value);
  // 1. call the function
  // 2. pass the function the value you need later
}

const Search = (props) => {
  const [searchTerm, setSearchTerm] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const renderSearchResults = () => <div>results</div>

  return (
    <div className="searchForm">
      <input
        type="text"
        placeholder="Search"
        value={searchTerm}
        onChange={(e) => {getDebouncedHandler(e, (e) => {handleChange(e, searchTerm, setSearchTerm, setSearchResults); }, 100)}}
      />
      {renderSearchResults()}
    </div>
  );
}

export default Search;

这是一个codesandbox link https://codesandbox.io/s/wonderful-liskov-dj07t?file=/src/App.js:0-1268

为了澄清,我保留了我的回答,以保持您的代码原样不变。但这不是正常模式,您应该使用 Ram 所指的样式,在其中创建去抖动函数的实例并调用它,而不是在每次击键时都创建一个新的去抖动函数,这并不理想。

这是一种实现方法:

  1. 不要去抖 onChange 处理程序,这会导致基本用户体验,因为一些字符会丢失。
  2. 创建昂贵函数的去抖动版本。通常这是您拨打 API 电话的地方。我假设你的情况是 doSearch
  3. 如果你使用的是函数式组件,请将去抖函数保留在你的组件之外,或者使用 useMemo 保留引用,否则每次都会创建它,而且它不会很有用(它有一个跟踪上次调用和其他内容的内部状态...)。
  4. 请记住,当您调用 _.debounce 时,它 returns 是一个函数。
  5. 使用 useEffect 在搜索查询发生变化时调用去抖功能。

代码:

// Your expensive function
const doSearch = (query, callback) => {
  const searchUrl = "/coops/?contains=" + encodeURIComponent(query);
  fetch(searchUrl, {
    method: "GET",
  })
    .then((response) => response.json())
    .then((data) => {
      callback(data);
    });
}

// The debounced version
const doSearchDebounced = _.debounce(doSearch, 100);

const Search = (props) => {
  const [searchTerm, setSearchTerm] = useState('');
  const [searchResults, setSearchResults] = useState([]);

  // Keep track of the last request ID
  const lastRequestId = useRef(null);

  // This hook will run when searchTerm changes
  useEffect(() => {
    if (!searchTerm) {
      setSearchResults([]);
      return;
    }

    // Update the last request ID
    const requestId = performance.now();
    lastRequestId.current = requestId;

    // Let the debounced function do it's thing
    doSearchDebounced(searchTerm, (results) => {
      // Only set the data if the current request is the last one
      if (requestId === lastRequestId.current) {
        setSearchResults(results);
      }
    });

  }, [searchTerm]);

  // No debounce here, just getting the user's input
  const handleChange = (e) => {
    setSearchTerm(e.target.value);
  };

  const renderSearchResults = ...

  return (
    <div className="searchForm">
      <input
        type="text"
        placeholder="Search"
        value={searchTerm}
        onChange={handleChange}
      />
      {renderSearchResults()}
    </div>
  );
}

可能是因为_.debounce returns一个去抖动的函数,它不谴责对函数的调用。

...

const handleChange = (event, searchTerm, setSearchTerm, setSearchResults) => {
  console.log("search term:" + searchTerm);
  const query = event.target.value;
  setSearchTerm(query);
  if (!query) {
    setSearchResults( [] );
  } else {
    doSearch(query, searchTerm, setSearchResults);
  }
}

const getDebouncedHandler = (e, handler, delay) => {
  console.log("value:" + e.target.value);
  return _.debounce(handler, delay);
}

const denouncedHandler = getDebouncedHandler((e, searchTerm, setSearchTerm, setSearchResults) => {
  handleChange(e, searchTerm, setSearchTerm, setSearchResults);
}, 
100);
...

const Search = (props) => {
  const [searchTerm, setSearchTerm] = useState('');
  const [searchResults, setSearchResults] = useState([]);

  const renderSearchResults = ...

  return (
    <div className="searchForm">
      <input
        type="text"
        placeholder="Search"
        value={searchTerm}
        onChange={(e) => denouncedHandler(e, searchTerm, setSearchTerm, setSearchResults)}
      />
      {renderSearchResults()}
    </div>
  );
}

export default Search;