如何通过去抖动提高 React 中的搜索性能

在 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 调用时,使用去抖函数是一个很好的选择。此函数在发送后端请求之前引入了一个延迟。

这有助于减少发送到服务器的请求数量,因为它仅在延迟时间过去且用户停止输入后才发送请求。这样,服务器就不会因过多的请求而过载,并且性能可以保持高效。