在 React 应用中构建搜索功能时,每次用户在输入框内输入内容,都会触发 `onChange` 事件,进而调用搜索函数。这种频繁的调用在处理 API 请求或数据库查询等操作时,可能会引发性能瓶颈。过于频繁地调用搜索功能会给服务器带来巨大压力,甚至导致服务器崩溃或界面无响应。为了解决这一问题,去抖(Debouncing)技术应运而生。
什么是去抖?
在 React 中,实现搜索功能的常见方法是在每次按键时触发 `onChange` 事件并调用相应的处理函数,如下面的代码所示:
import { useState } from "react";export default function Search() {
const [searchTerm, setSearchTerm] = useState("");const handleSearch = () => {
console.log("正在搜索:", searchTerm);
};const handleChange = (e) => {
setSearchTerm(e.target.value);
handleSearch();
};return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="在此搜索..."
/>
);
}
虽然这种方法在功能上可行,但每次击键都请求后端更新搜索结果会消耗大量资源。例如,当用户输入“webdev”时,应用会向后端发送包含“w”,“we”,“web”等值的一系列请求,这对性能会产生负面影响。
去抖是一种延迟函数执行的技术。当用户触发事件时(如按键),去抖函数会启动一个计时器,在指定的时间间隔内阻止搜索处理函数的调用。如果用户在延迟时间内继续输入,计时器会被重置,并在新的延迟时间过后再次调用该函数。这个过程会持续进行,直到用户停止输入为止。
通过等待用户停止输入,去抖技术确保应用只发送必要的搜索请求,从而减轻服务器的负担。
如何在 React 中使用去抖技术优化搜索功能
您可以使用各种库来实现去抖功能,或者使用 JavaScript 的 `setTimeout` 和 `clearTimeout` 函数自行实现。本文将使用 `lodash` 库中的 `debounce` 函数。
假设您已创建了一个 React 项目,请创建一个名为 `Search` 的新组件。如果还没有项目,可以使用 `create-react-app` 工具创建一个新的 React 应用。
在 `Search` 组件文件中,复制以下代码以创建一个搜索输入框,该输入框会在每次击键时调用处理函数。
import { useState } from "react";export default function Search() {
const [searchTerm, setSearchTerm] = useState("");const handleSearch = () => {
console.log("正在搜索:", searchTerm);
};const handleChange = (e) => {
setSearchTerm(e.target.value);
handleSearch();
};return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="在此搜索..."
/>
);
}
为了对 `handleSearch` 函数应用去抖技术,请将其传递给 `lodash` 的 `debounce` 函数。
import debounce from "lodash.debounce";
import { useState } from "react";export default function Search() {
const [searchTerm, setSearchTerm] = useState("");const handleSearch = () => {
console.log("正在搜索:", searchTerm);
};
const debouncedSearch = debounce(handleSearch, 1000);const handleChange = (e) => {
setSearchTerm(e.target.value);
debouncedSearch();
};return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="在此搜索..."
/>
);
}
在 `debounce` 函数中,您需要传入要延迟执行的函数(即 `handleSearch` 函数)以及延迟的时间(以毫秒为单位,例如 500 毫秒)。
虽然上述代码应该可以延迟调用 `handleSearch` 函数,直到用户停止输入,但它在 React 中并不像预期的那样工作。 我们将在下一节中解释原因。
去抖与重新渲染
该应用使用受控组件,这意味着输入框的值由状态值控制。每次用户在搜索字段中输入内容时,React 都会更新状态。
在 React 中,当状态值发生变化时,React 会重新渲染组件并执行组件中的所有函数。
在上面的搜索组件中,当组件重新渲染时,React 会执行 `debounce` 函数。 该函数会创建一个新的计时器来跟踪延迟,而旧的计时器仍然存在于内存中。当时间过去后,旧的计时器会触发搜索功能。这意味着搜索功能永远不会真正去抖,只会延迟 500 毫秒。每次重新渲染都会重复这个循环——该函数创建一个新的计时器,旧的计时器到期,然后调用搜索函数。
为了让去抖函数正常工作,您需要确保它只被调用一次。您可以通过将去抖函数定义在组件之外或使用记忆化技术来实现。这样,即使组件重新渲染,React 也不会再次执行它。
在搜索组件外部定义去抖函数
将去抖函数移动到搜索组件之外,如下所示:
import debounce from "lodash.debounce"const handleSearch = (searchTerm) => {
console.log("正在搜索:", searchTerm);
};const debouncedSearch = debounce(handleSearch, 500);
现在,在搜索组件中,调用 `debouncedSearch` 并传入搜索词。
export default function Search() {
const [searchTerm, setSearchTerm] = useState("");const handleChange = (e) => {
setSearchTerm(e.target.value);
debouncedSearch(searchTerm);
};return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="在此搜索..."
/>
);
}
现在,只有在延迟时间过后才会调用搜索函数。
记忆化去抖函数
记忆化是一种缓存函数结果的技术,当使用相同的参数再次调用该函数时,会直接返回缓存的结果,而不是重新计算。
要记忆化去抖函数,可以使用 `useMemo` Hook。
import debounce from "lodash.debounce";
import { useCallback, useMemo, useState } from "react";export default function Search() {
const [searchTerm, setSearchTerm] = useState("");const handleSearch = useCallback((searchTerm) => {
console.log("正在搜索:", searchTerm);
}, []);const debouncedSearch = useMemo(() => {
return debounce(handleSearch, 500);
}, [handleSearch]);const handleChange = (e) => {
setSearchTerm(e.target.value);
debouncedSearch(searchTerm);
};return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="在此搜索..."
/>
);
}
请注意,您还需要将 `handleSearch` 函数包装在 `useCallback` Hook 中,以确保 React 只调用它一次。如果没有 `useCallback` Hook,React 将会在每次重新渲染时执行 `handleSearch` 函数,这会导致 `useMemo` Hook 的依赖项发生变化,从而调用 `debounce` 函数。
现在,只有当 `handleSearch` 函数或延迟时间发生变化时,React 才会调用 `debounce` 函数。
使用去抖优化搜索
有时,放慢速度反而能提高性能。 在处理搜索功能时,尤其是当涉及到开销较大的数据库或 API 调用时,使用去抖函数是一个很好的选择。此函数在发送后端请求之前引入了一个延迟。
这有助于减少发送到服务器的请求数量,因为它仅在延迟时间过去且用户停止输入后才发送请求。这样,服务器就不会因过多的请求而过载,并且性能可以保持高效。