我可以在另一个 React.useCallback 中使用 React.useCallback 吗?

Joh*_*ohn 5 optimization reactjs

有一个渲染用户卡的组件

\n

\r\n
\r\n
import React from "react";\n\nconst User = React.memo(function({id, name, isSelected, ...other}) {\n  return (\n    <div {...other}>\n      {name} - {isSelected && "Selected"}\n    </div>\n  );\n});\n\nexport default User;
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

以及渲染用户卡的父组件

\n

\r\n
\r\n
import React from "react";\n\nfunction Application() {\n  const [users, setUsers] = React.useState([\n    {id: 1, name: "John Doe #1"},\n    {id: 2, name: "John Doe #2"},\n    {id: 3, name: "John Doe #3"}\n  ]);\n  const [selectedUserId, setSelectedUserId] = React.useState(null);\n\n  return users.map((user) => {\n    const isSelected = selectedUserId === user.id;\n\n    return (\n      <User\n        {...user}\n        key={user.id}\n        isSelected={isSelected}\n        onClick={() => setSelectedUserId(user.id)}\n      />\n    );\n  });\n}\n\nexport default Application;
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

任务是“选择用户后避免重新渲染其他用户卡”

\n

我尝试过使用React.useCallback钩子,这是我的第一个实现

\n

\r\n
\r\n
import React from "react";\n\nconst User = React.memo(function({id, name, isSelected, ...other}) {\n  return (\n    <div {...other}>\n      {name} - {isSelected && "Selected"}\n    </div>\n  );\n});\n\nfunction Application() {\n  const [users, setUsers] = React.useState([\n    {id: 1, name: "John Doe #1"},\n    {id: 2, name: "John Doe #2"},\n    {id: 3, name: "John Doe #3"}\n  ]);\n  const [selectedUserId, setSelectedUserId] = React.useState(null);\n\n  const handleSelectUser = React.useCallback((userId) => () => {\n    setSelectedUserId(userId);\n  }, []);\n\n  return users.map((user) => {\n    const isSelected = selectedUserId === user.id;\n\n    return (\n      <User\n        {...user}\n        key={user.id}\n        isSelected={isSelected}\n        onClick={handleSelectUser(user.id)}\n      />\n    );\n  });\n}\n\nexport default Application;
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

在这种情况下,React.useCallback返回一个具有新引用的匿名函数

\n

结果:每次点击后所有用户卡仍然重新渲染

\n

我决定将这个匿名函数包装在React.useCallback

\n

\r\n
\r\n
import React from "react";\n\nconst User = React.memo(function({id, name, isSelected, ...other}) {\n  return (\n    <div {...other}>\n      {name} - {isSelected && "Selected"}\n    </div>\n  );\n});\n\nfunction Application() {\n  const [users, setUsers] = React.useState([\n    {id: 1, name: "John Doe #1"},\n    {id: 2, name: "John Doe #2"},\n    {id: 3, name: "John Doe #3"}\n  ]);\n  const [selectedUserId, setSelectedUserId] = React.useState(null);\n\n  const handleSelectUser = React.useCallback((userId) => {\n    return React.useCallback(() => {\n      setSelectedUserId(userId);\n    }, []);\n  }, []);\n\n  return users.map((user) => {\n    const isSelected = selectedUserId === user.id;\n\n    return (\n      <User\n        {...user}\n        key={user.id}\n        isSelected={isSelected}\n        onClick={handleSelectUser(user.id)}\n      />\n    );\n  });\n}\n\nexport default Application;
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

问题解决了,但是还有一个问题,我做对了吗?React 团队说:Don\xe2\x80\x99t 在循环、条件或嵌套函数内调用 Hooks,我会得到什么副作用?

\n

PS 请勿触摸User组件

\n

Ada*_*dam 0

解释为什么不能在钩子内部调用钩子(并期望它能够一致且可靠地工作)

为什么你不能在钩子内部调用钩子 - 去这里进行超深入的研究,比我在这个答案中需要提供的更多上下文https://overreacted.io/why-do-hooks-rely-on-call-order /

您的解决方案之所以有效,是因为尽管“违反了规则”,但对挂钩的调用顺序始终相同......直到用户被添加或从状态中删除。

您绝对可以按照所写的方式使用您的解决方案。但是,如果您需要更改用户数量,会发生什么情况呢?


不,你不能在钩子内使用钩子。它可能“有效”,但 React 告诉你它无法可靠地工作,并且你做错了。必须在自定义钩子顶层组件的顶层调用钩子。

您走在正确的道路上,但解决问题的方法是

  1. 使用提供给回调函数的参数作为单击的元素的源 AND
  2. 如果您无法修改用户组件,那么您必须提供有关该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);

  // this callback is referentially stable - it doesn't change between renders because it has no dependencies
  const handleSelectUser = React.useCallback((e) => {
    setSelectedUserId(+e.target.getAttribute('data-userid'));
  }, []);

  return users.map((user) => {
    const isSelected = selectedUserId === user.id;

    return (
      <User
        {...user}
        data-userid={user.id} <- this line
        key={user.id}
        isSelected={isSelected}
        onClick={handleSelectUser}
      />
    );
  });
}
Run Code Online (Sandbox Code Playgroud)