Can I use React.useCallback inside another React.useCallback?
P粉153503989
2023-09-01 14:55:58
<p>有一个渲染用户卡片的组件</p>
<p>
<pre class="brush:js;toolbar:false;">import React from "react";
const User = React.memo(function({id, name, isSelected, ...other}) {
return (
<div {...other}>
{name} - {isSelected && "Selected"}
</div>
);
});
export default User;</pre>
</p>
<p>以及渲染用户卡的父组件</p>
<p>
<pre class="brush:js;toolbar:false;">import React from "react";
function Application() {
const [users, setUsers] = React.useState([
{id: 1, name: "John Doe #1"},
{id: 2, name: "John Doe #2"},
{id: 3, name: "John Doe #3"}
]);
const [selectedUserId, setSelectedUserId] = React.useState(null);
return users.map((user) => {
const isSelected = selectedUserId === user.id;
return (
<User
{...user}
key={user.id}
isSelected={isSelected}
onClick={() => setSelectedUserId(user.id)}
/>
);
});
}
export default Application;</pre>
</p>
<p>任务是“选择用户后避免重新渲染其他用户卡”</p>
<p>我尝试使用 <code>React.useCallback</code> 钩子,这是我的第一个实现</p>
<p>
<pre class="brush:js;toolbar:false;">import React from "react";
const User = React.memo(function({id, name, isSelected, ...other}) {
return (
<div {...other}>
{name} - {isSelected && "Selected"}
</div>
);
});
function Application() {
const [users, setUsers] = React.useState([
{id: 1, name: "John Doe #1"},
{id: 2, name: "John Doe #2"},
{id: 3, name: "John Doe #3"}
]);
const [selectedUserId, setSelectedUserId] = React.useState(null);
const handleSelectUser = React.useCallback((userId) => () => {
setSelectedUserId(userId);
}, []);
return users.map((user) => {
const isSelected = selectedUserId === user.id;
return (
<User
{...user}
key={user.id}
isSelected={isSelected}
onClick={handleSelectUser(user.id)}
/>
);
});
}
export default Application;</pre>
</p>
<p>In this case, <code>React.useCallback</code> returns an anonymous function</p> with a new reference
<p><strong>Result: All user cards still re-render after each click</strong></p>
<p>I decided to wrap this anonymous function in <code>React.useCallback</code></p>
<p>
<pre class="brush:js;toolbar:false;">import React from "react";
const User = React.memo(function({id, name, isSelected, ...other}) {
return (
<div {...other}>
{name} - {isSelected && "Selected"}
</div>
);
});
function Application() {
const [users, setUsers] = React.useState([
{id: 1, name: "John Doe #1"},
{id: 2, name: "John Doe #2"},
{id: 3, name: "John Doe #3"}
]);
const [selectedUserId, setSelectedUserId] = React.useState(null);
const handleSelectUser = React.useCallback((userId) => {
return React.useCallback(() => {
setSelectedUserId(userId);
}, []);
}, []);
return users.map((user) => {
const isSelected = selectedUserId === user.id;
return (
<User
{...user}
key={user.id}
isSelected={isSelected}
onClick={handleSelectUser(user.id)}
/>
);
});
}
export default Application;</pre>
</p>
<p>The problem is solved, but there is still one question, did I do it right? The React team says: <em>Don't call Hooks inside loops, conditionals, or nested functions</em> What side effects will I get? </p>
<p>Note do not touch the <code>user</code>component</p>
Explain why a hook cannot be called from within a hook (and expect it to work consistently and reliably)
Why you can't call a hook inside a hook - go here for a super deep dive with more context than I need to provide in this answer https://overreacted.io/why-do-hooks -rely-on-call-order/
Your solution works because despite "breaking the rules" the order of calls to the hooks is always the same... until the user is added or removed from the state.
You can definitely use the solution you wrote. But what if you need to change the number of users?
No, you cannot use hooks inside hooks. It might "work", but React is telling you that it doesn't work reliably and that you're doing it wrong. The hook must be called at the top level of the custom hook's top-level component.
Your direction is correct, but the solution to the problem is
div
element to let you know which user element was clicked.It looks like this: