【问题标题】:React hooks: is `useCallback` not so needed usually?React hooks:通常不需要`useCallback`吗?
【发布时间】:2020-02-25 10:21:57
【问题描述】:

我最近正在使用 React Hooks 重构一个 Web 应用程序。我遇到了关于useCallback 的问题。根据 Kent 的描述:https://kentcdodds.com/blog/usememo-and-usecallback,useCallback 是将相同的函数引用传递给子组件,避免子组件的重新渲染,从而使性能更好。但是,它与React.memo 一起使用。正如肯特所说:

大多数情况下,您不应该费心优化不必要的渲染。 React 非常快,我能想到的有很多事情可以让你用你的时间来做,这比优化这样的事情要好。事实上,用我将要展示给你的东西来优化东西的需求是如此罕见,以至于我实际上从来不需要这样做......

所以,我的问题是:我是否有权声称我们通常不需要使用useCallback?除非回调的创建成本很高,否则使用 useCallback 可以避免为每个渲染重新创建回调。

说,对于onClickonChange 事件处理程序,2 行或更少,我们是否应该不使用useCallback 来包装它?

【问题讨论】:

  • Kent 的文章大致总结了同样的结论
  • 我发现唯一需要它的情况是,回调最终是否变成了使用备忘录或使用效果
  • 将回调从列表组件传递给它的项目,如果回调会导致项目更改,则 DOM 会重新呈现所有项目。这是因为回调每次都会有一个新的引用,并且虚拟 DOM 比较会失败。因此,如果您有 100 个项目并更改了一个,那么 99 个未更改的项目将无缘无故地 DOM 呈现 here 是一个示例,以防止使用有状态组件和 here 使用 react-redux 容器。
  • 既然这是肯特的观点,我会给出相反的观点。当使用重选和纯组件时,不使用 useCallback 是没有意义的,因为不使用它会使纯组件和从状态中记忆计算的道具无用。当组件没有任何变化时重新渲染组件也会使调试变得困难。
  • 我认为他不使用它的主要原因是:it can be tricky to get right all the time so you may not be reaping any benefits at all anyway. 换句话说; “这很难,所以不要打扰”。我确实同意这很困难,但是当您定义容器/展示组件并在容器中使用选择器时会容易得多(无论如何您都应该这样做)。

标签: reactjs react-hooks usecallback


【解决方案1】:

当我不想更改函数引用时,我发现useCallback() 是必要的。例如,当我在某个子组件上使用 React.memo 时,由于其方法之一通过 props 的引用更改而不应重新渲染。

示例:

在下面的示例中,如果 Parent 重新渲染,Child1 将始终重新渲染,因为 parentMethod1 将在每次渲染时获得新的引用。并且Child2 不会重新渲染,因为parentMethod2 将在渲染之间保留其引用(您可以传递一个依赖数组以使其更改并在新输入值出现时重新创建)。

注意:假设Child 组件被React.memo() 记忆

function Parent() {
  const parentMethod1 = () => DO SOMETHING;
  const parentMethod2 = useCallback(() => DO SOMETHING,[]);
  return(
    <React.Fragment>
    <Child1
      propA=parentMethod1
    />
    <Child2
      propA=parentMethod2
    />
    </React.Fragment>
  );
}

另一方面,如果function 的运行成本很高,您可以使用useMemo 挂钩来记忆其结果。然后你只会在新值出现时运行它,否则它会给你一个使用这些相同值的先前计算的记忆结果。

https://reactjs.org/docs/hooks-reference.html#usecallback

使用回调

传递一个内联回调和一个依赖数组。 useCallback 将返回回调的记忆版本,仅当其中一个依赖项发生更改时才会更改。这在将回调传递给依赖引用相等以防止不必要的渲染(例如shouldComponentUpdate)的优化子组件时很有用。

使用备忘录

传递一个“create”函数和一个依赖数组。 useMemo 只会在依赖项之一发生更改时重新计算记忆值。这种优化有助于避免在每次渲染时进行昂贵的计算。

【讨论】:

    【解决方案2】:

    我认为你是对的。从它的设计方式来看,useCallback 在 React 中应该几乎没用。它不能直接用于防止子渲染。

    可以保存子渲染的方法是使用useMemo 包装整个渲染。

    const Title = () => {
      ...
      const child = useMemo(() => {
        return <Child a={"Hello World"} />
      }, [])
      return (
        <>
          {child}
          <div onClick={onClick}>{count}</div>
        </>
      )
    }
    

    上述方法与React.memo 有点不同,因为它直接作用于父 Title,而不是 Child。但它或多或少回答了你的问题,为什么它没用,除非你把它用作useMemo 的快捷方式。

    文章对此进行了解释,https://javascript.plainenglish.io/can-usememo-skip-a-child-render-94e61f5ad981

    回到使用回调

    现在让我们回过头来看看使用或不使用useCallback 包装的回调是否有用。

    <div onClick={onClick}>kk</div>
    

    它唯一可能节省的是,当它处于对账状态时,onClick(带有 useCallback)指向同一个函数实例。

    但是我不知道 React 是否真的在这一步做了任何优化。因为为属性分配不同的回调可能需要额外的内存和时间。但是添加一个新变量通常也需要额外的内存。

    所以这种类型的优化更像是一种编码优化,或多或少是主观的。不够客观,无法应用于可靠的案例。

    当然,如果您想为任何第三方函数修复函数实例,例如。 debounce。这可能是一个很好的用途,但仍然闻起来很腥,因为useMemo 似乎也更能涵盖这种情况。

    总而言之,我只是指出,useCallback 并没有做公众认为它可以做的事情,例如救助子组件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-15
      • 1970-01-01
      • 1970-01-01
      • 2020-11-01
      • 2020-04-26
      • 2021-05-23
      • 2020-02-15
      • 2023-01-16
      相关资源
      最近更新 更多