【问题标题】:React hooks - Prevent rerender if parent state that holds children props changedReact hooks - 如果持有子道具的父状态发生更改,则防止重新渲染
【发布时间】:2021-11-14 15:27:07
【问题描述】:

我当前使用 React 钩子的项目存在问题。 我要做的只是使用 (shift+click) 来选择我的任务。看起来像这样:

代码如下:

  ...
  const [selectedTaskIds, setSelectedTaskIds] = useState<string[]>([])
  const selectTask = useCallback(
    (e: MouseEvent<HTMLDivElement>, taskId: string): void => {
      e.stopPropagation()

      const previousTaskId = selectedTaskIds[selectedTaskIds.length - 1]
      if (previousTaskId && e.shiftKey) {
        // handle shift+click
        const previousIdx = tasks.findIndex((task) => task.id === previousTaskId)
        const selectedIdx = tasks.findIndex((task) => task.id === taskId)
        const rangeTasks =
          previousIdx < selectedIdx
            ? tasks.slice(previousIdx, selectedIdx + 1)
            : tasks.slice(selectedIdx, previousIdx + 1)
        const rangeIds = rangeTasks.map((task) => task.id)
        setSelectedTaskIds([...new Set([...selectedTaskIds, ...rangeIds])])
      } else {
        // if no key clicked, just select 1 task item
        setSelectedTaskIds([taskId])
      }
    },
    [selectedTaskIds, tasks] // <==== in here I notice that activeTaskIds is changed overtime that causes all of my <TaskItem> rerender
  )

  return (
      {tasks.map((task) => (
          <TaskItem
            key={task.id}
            taskId={task.id}
            onClick={selectTask} // <=== selectTask will be different if user click on one of the task items
            active={selectedTaskIds.includes(task.id)}
          />
        ))}
   )

问题是,要知道当用户使用shift+click时我应该选择哪些任务,我需要知道当前选择的任务id,所以我需要将selectedTaskIds作为useCallback()deps传递。

这使得每当用户选择任务,甚至只是单击其中一个任务项来选择任务时,它都会重新呈现我的 all 我的&lt;TaskItem&gt;,因为selectTask()由于 useCallback 的 deps 发生了函数变化。

如何在不重新渲染我的所有 &lt;TaskItem&gt;s 的情况下解决这个问题?非常感谢!

【问题讨论】:

  • 您是否尝试过不将任何deps 传递给useCallback(我的意思是空的[])?然后会发生什么?
  • 是的,我这样做了,它不会重新渲染所有子项,但是当我使用 shift+click 选择多个任务项时,它无法按预期工作(仅选择了一项)

标签: reactjs optimization react-hooks react-typescript rerender


【解决方案1】:

我在我的机器上测试了您的代码并测试了一些场景。据我所知,组件重新渲染所有&lt;TaskItem&gt;s 看起来很自然,因为selectedTaskIds 状态的任何更改都将保证组件内包含selectedTaskIds 的所有内容都可以渲染。举个具体的例子,

<div className="App">
    <TaskItems />
    <div>hahaha</div>
    <div>selectedTaskIds</div>
</div>

假设你有上面的代码。 (我将包含多个&lt;TaskItem/&gt;s 的组件命名为&lt;TaskItems/&gt;)当onClick&lt;TaskItem/&gt; 触发时,只有&lt;TaskItems/&gt; 会重新渲染。其他两个 div 不会重新渲染。但是,如果将两个 div 放在 &lt;TaskItems/&gt; 组件内,它们将重新渲染:

// assuming this is inside <TaskItems/>
...
return (
<div>
  {tasks.map((task) => (
      <TaskItem
        key={task.id}
        taskId={task.id}
        onClick={(e) => { selectTask2(e, task.id)}} // <=== selectTask will be different if user click on one of the task items
        // active={selectedTaskIds.includes(task.id)}
        active={true}
        title={task.title}
      />
    ))}
    <div>hahaha</div>
    <div>selectedTaskIds</div>
</div>

);

上面的代码会重新渲染两个 div。

我已尝试满足您的要求,以摆脱未更改的任务的重新渲染,但这真的很难做到。当我试图防止重新渲染时,我通常使用以下两种技术之一:

  1. 创建一个子组件并分离代码库以隔离状态组。 (因为状态是触发渲染的东西,你可以 将不相关的人分成不同的组。)

  2. useCallback/useMemo

我未能针对您的案例实施这两种技术,但可能有一种方法可以应用上述技术。我会关注这个帖子,看看有没有其他人得到解决方案。

【讨论】:

  • 如果我有什么想法,我会在接下来的几个小时内更新帖子。
  • 嗨 Seho,非常感谢您的尝试!是的,我也试图弄清楚,但找不到更好的方法来摆脱其他任务的重新渲染:(我也想分离组件,但我认为结果将是相同的,因为它最终需要 selectedTaskIds
猜你喜欢
  • 1970-01-01
  • 2021-02-26
  • 1970-01-01
  • 1970-01-01
  • 2019-07-28
  • 2021-07-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多