Dead Simple Chat 允许您使用强大的Javascript Chat SDK轻松地将 Chat 添加到任何 React 应用程序。
在这篇博文中,我们将看到如何使用 React useCallback
hook。
它的目的是什么,我们还将研究应该使用它的真实场景以及使用 React 时要避免的常见陷阱 useCallback
。
useCallback
用于缓存函数“定义”。它通常与React memo结合使用 。
如果你已经使用 React memo 缓存了你的组件,那么它不会重新渲染,除非它的 props 被改变。
如果您向组件传递一个函数,那么您的函数将每次都重新渲染,从而违背了使用 memo 的目的。
因为在 Javascript 中 function() {}
还是 () => { }
创建了不同的函数,因此 prop 永远不会相同,导致组件重新渲染,make memo
无用。
为防止这种情况发生,您可以将函数定义包装在里面 useCallback
,这样可以防止重新创建函数,除非依赖关系发生变化。
useCallback
该 useCallback
钩子接受两个参数,一个是你想要缓存的方法/函数,第二个参数是依赖数组,它返回缓存的函数。
当依赖数组中传递的变量发生变化时, useCallback
钩子返回并更新函数。
const method = useCallback(<METHOD>, [<DEPENDENCY_ARRAY]);
让我们看一些例子来更好地理解它。
Dead Simple Chat 允许您使用功能强大的 Javascript Chat SDK 在几分钟内将聊天集成到您的 React 或 Web 应用程序中 。
useCallback
考虑以下代码:
import React, { useState } from "react";
const ChildComponent = React.memo(({ onButtonClick }) => {
console.log("ChildComponent rendered");
return <button onClick={onButtonClick}>Increment</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [theme, setTheme] = useState("light");
const handleButtonClick = () => {
setCount(count + 1);
};
return (
<div>
<h1>Current Theme: {theme}</h1>
<button
onClick={() => {
theme === "light" ? setTheme("dark") : setTheme("light");
}}
>
Toggle Theme
</button>
<h1>Counter: {count}</h1>
<ChildComponent onButtonClick={handleButtonClick} />
</div>
);
}
export default ParentComponent;
在上面的代码中,我们创建了一个 ChildComponent
并使用 React. memo
.
React 备忘录的简要介绍:
React.memo
除非更改道具,否则使用组件不会重新渲染。
通常,当重新渲染父组件时,所有子组件也会重新渲染。
但是通过在子组件上使用 React memo,当父组件被重新渲染时,子组件不会被重新渲染,除非子组件的 props 发生变化。
在中, ChildComponent
我们还添加了一条 console.log
语句以在控制台上打印“ChildComponent rendered”。
我们正在接受一个方法 onButtonClick
作为 的道具 ,并 在按下按钮时ChildComponent
调用该事件的方法 。onClick
接下来,我们创建了一个 ParentComponent
并在其中 ParentComponent
创建了两个状态变量,一个是 count
,另一个是 theme
。
在中, ParentComponent
我们创建了一个名为 as 的方法 handleButtonClick
,它会增加我们的状态变量 count
,我们将此方法作为 prop 传递给 ChildComponent
.
我们还创建了一个按钮来切换称为 the 的第二个状态变量, theme
当按下按钮时,我们将主题从浅色切换到深色。
我们还显示当前主题和计数。
当我们使用 React.memo 时,我们预期的行为是当我们切换主题时,我们不应该 ChildComponent rendered
在屏幕上看到消息。
让我们试试看:
正如您在上面的视频中看到的那样,每次按下“切换主题”按钮时,屏幕上都会打印“已呈现子组件”消息。
为什么会这样?
我们在 React useCallback 介绍中讨论过,它的发生是因为 JavaScript 每次渲染组件时都会创建一个新函数。
我们将函数作为 prop 传递,React memo 会将其视为新函数并重新渲染子组件。
为了解决这个问题,我们将包装我们的函数 useCallback
,它将返回我们函数的缓存版本。
这是更新后的代码:
import React, { useState, useCallback } from "react";
const ChildComponent = React.memo(({ onButtonClick }) => {
console.log("ChildComponent rendered");
return <button onClick={onButtonClick}>Click me</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [theme, setTheme] = useState("light");
// Using useCallback to cache the function
const handleButtonClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<h1>Current Theme: {theme}</h1>
<button
onClick={() => {
theme === "light" ? setTheme("dark") : setTheme("light");
}}
>
Toggle Theme
</button>
<h1>Counter: {count}</h1>
<ChildComponent onButtonClick={handleButtonClick} />
</div>
);
}
export default ParentComponent;
在我们更新的代码中,我们将函数包装在 useCallback
钩子中。让我们看看更新后的代码如何执行:
正如您在控制台中看到的那样,每次我们按下“切换主题”按钮时,都不会打印“ChildComponent rendered”消息。
让我们看看在哪些地方使用 useCallback
.
useCallback
我们将查看一些带有代码示例的真实场景,在这些场景中使用 using useCallback
会很有用。
在无限滚动列表中,我们可以使用 来 useCallback
缓存负责获取数据的函数,以防止不必要的渲染和 API 调用。
让我们看一个示例,我们将使用免费提供的 Github List Users API 构建一个简单的无限滚动列表。
在本例中,我们将 结合使用useCallback
和 来防止不必要的 API 调用。useEffect
import { useState, useEffect, useCallback } from "react";
function InfinitUserScroll() {
const [users, setUsers] = useState([]);
const [page, setPage] = useState(1);
const fetchData = useCallback(async () => {
const response = await fetch(
`https://api.github.com/users?since=${page * 30}`
);
const nextData = await response.json();
setUsers((curData) => [...curData, ...nextData]);
}, [page]);
useEffect(() => {
fetchData();
}, [fetchData]);
const handleScroll = (e) => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
if (scrollHeight - scrollTop === clientHeight) {
setPage((prevPage) => prevPage + 1);
}
}
return (
<>
<div
onScroll={handleScroll}
style={{ overflowY: "scroll", height: "400px" }}
>
<h1>Github Users</h1>
<hr />
{users.map((item, index) => (
<div key={index}>{item.login}</div>
))}
</div>
</>
);
}
export default InfinitUserScroll;
在上面的代码中,我们用来 useCallback
缓存方法的一个是 fetchData
方法。
该 fetchData
方法被缓存, page
状态变量是传递给缓存 fetchData 方法的 useCallback 的依赖项。
然后我们 fetchData
作为依赖项传递 useEffect
并调用 fetchData
方法 useEffect
。
列为 page
依赖项 useCallback
导致 fetchData
仅当值更改时才重新创建方法 page
。
当 值更改时, 将创建 page
新 方法,这会导致 触发以获取新页面信息。fetchData
useEffect
在更新页面值之前, fetchData
不会调用该方法,从而防止不必要的 API 调用。
我们还可以使用 useCallback
hook 去抖动调用 API(例如搜索 API)的用户输入。
消除用户输入的抖动可防止对 API 的过度调用,从而防止服务器过载,或者如果您使用的是第 3 方 API,则可防止 API 成本。
让我们在代码示例中编写代码:
import React, { useState, useEffect, useCallback } from "react";
import debounce from "lodash.debounce";
function WikiSearch({ searchDelay = 300 }) {
const [searchTerm, setSearchTerm] = useState("");
const [results, setResults] = useState([]);
const performSearch = async (term) => {
const response = await fetch(
`https://en.wikipedia.org/w/api.php?action=query&list=search&format=json&origin=*&srsearch=${term}`
);
const data = await response.json();
setResults(data.query.search);
};
const debouncedSearch = useCallback(
debounce((term) => {
performSearch(term);
}, searchDelay),
[searchDelay]
);
useEffect(() => {
if (searchTerm) {
debouncedSearch(searchTerm);
} else {
setResults([]);
}
}, [searchTerm, debouncedSearch]);
return (
<div>
<h3>Wiki Search Engine</h3>
<hr />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search Wikipedia."
/>
{results.length > 0 ? (
<h1>Wikipedia Search Results</h1>
) : (
<div>
<br />
<strong>Nothing Found</strong>
</div>
)}
<ul>
{results.map((result) => (
<li key={result.pageid}>
<a
href={`https://en.wikipedia.org/?curid=${result.pageid}`}
target="_blank"
rel="noreferrer"
>
{result.title}
</a>
</li>
))}
</ul>
</div>
);
}
export default WikiSearch;
在上面的代码示例中,我们使用维基百科 API 来搜索维基百科以获取页面列表。
我们正在添加 300 毫秒的默认去抖动并使用 lodash.debounce
库并使用创建缓存的去抖动方法 useCallback
我们创建了一个 WikiSearch 接受可选 searchDelay
.
接下来,我们创建了一个名为 as 的方法 performSearch
,该方法将获取信息并将其设置在结果日期变量中。
使用 useCallback
,我们创建了一个 debouncedSearch 方法并调用了 搜索useCallback
挂钩 。debounce
最后在 useEffect
钩子中我们调用了 debouncedSearch
方法。
这是演示:
当您的项目列表中的每个项目都有一个事件处理程序时,我们可以使用 来 useCallback
缓存处理程序函数。
为了演示这一点,我们将创建一个 TodoList 组件,它将显示 Todo 列表。
import React, { useState, useCallback } from "react";
const TodoItem = React.memo(({ item, onToggle }) => (
<li>
<input
type="checkbox"
checked={item.completed}
onChange={() => onToggle(item.id)}
/>
{item.name}
</li>
));
function TodoListComponent() {
const [todos, setTodos] = useState([
{ id: 1, name: "Todo 1", completed: false },
{ id: 2, name: "Todo 2", completed: false },
{ id: 3, name: "Todo 3", completed: false },
{ id: 4, name: "Todo 4", completed: false }
]);
const handleToggle = useCallback((id) => {
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
return (
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} item={todo} onToggle={handleToggle} />
))}
</ul>
);
}
export default TodoListComponent;
我们已经创建了 handleToggle
方法并使用 缓存它 useCallback
,并将 传递 handleToggle
给
列表。
在这篇博文中,我们学习了如何使用 useCallback
以及真实场景和示例。