useCallback在React中做了什么?

RTW*_*RTW 48 javascript reactjs react-hooks

正如文档中所述,useCallback返回一个memoized回调.

传递内联回调和一组输入.useCallback将返回一个回忆的memoized版本,该版本仅在其中一个输入发生更改时才会更改.这在将回调传递给优化的子组件时非常有用,这些子组件依赖于引用相等性来防止不必要的渲染(例如,shouldComponentUpdate).

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
Run Code Online (Sandbox Code Playgroud)

但是它如何工作以及在React中最好使用它?

Yan*_*Tay 93

当您希望防止不必要的重新渲染以获得更好的性能时,最好使用此方法.

比较这两种将回调传递给从React Docs获取的子组件的方法:

1.渲染中的箭头功能

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <Button onClick={() => this.handleClick()}>Click Me</Button>;
  }
}
Run Code Online (Sandbox Code Playgroud)

2.绑定构造函数(ES2015)

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <Button onClick={this.handleClick}>Click Me</Button>;
  }
}
Run Code Online (Sandbox Code Playgroud)

假设<Button>实现为a PureComponent,第一种方法将导致<Button>每次<Foo>重新渲染时重新渲染,因为在每次render()调用中都会创建一个新函数.在第二种方式中,该handleClick方法仅在<Foo>构造函数中创建一次并在渲染之间重用.

如果我们使用钩子将两种方法转换为功能组件,那么它们就是等价物(有点):

1.渲染中的箭头功能 - >未记忆的回调

function Foo() {
  const handleClick = () => {
    console.log('Click happened');
  }
  return <Button onClick={handleClick}>Click Me</Button>;
}
Run Code Online (Sandbox Code Playgroud)

2.在构造函数中绑定(ES2015) - >记忆回调

function Foo() {
  const memoizedHandleClick = useCallback(
    () => console.log('Click happened'), [],
  ); // Tells React to memoize regardless of arguments.
  return <Button onClick={memoizedHandleClick}>Click Me</Button>;
}
Run Code Online (Sandbox Code Playgroud)

第一种方法是在每次调用功能组件时创建回调,但在第二种方式中,React会为您记忆回调函数,并且不会多次创建回调.

在大多数情况下,第一种方式做得很好.正如React docs所述:

在渲染方法中使用箭头函数是否可以?一般来说,是的,没关系,并且它通常是将参数传递给回调函数的最简单方法.

如果您确实遇到性能问题,请务必优化!

  • 然后,如果我的 handleClick 函数有一些数据获取或参数更改,那么我不应该使用 useCallback,是吗? (2认同)
  • 是的,如果您的返回值即使使用相同的参数也可能发生变化,那么您不应该使用 `useCallback`,因为返回值已被记忆。 (2认同)

Vil*_*nen 22

useCallbackuseMemo尝试绕过使用 React hooks 选择的函数式编程方法所带来的弱点。在Javascript中,每个实体,无论是函数、变量还是其他什么,都会在执行进入函数的代码块时被创建到内存中。对于 React 来说,这是一个大问题,因为它会尝试检测组件是否需要渲染。根据输入道具和上下文扣除重新渲染的需要。让我们看一个不带useCallback.

const Component = () => {
  const [counter, setCounter] = useState(0);

  const handleClick = () => {
    setCounter(counter + 1);
  }

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleClick}>+1</button>
  </div>
}
Run Code Online (Sandbox Code Playgroud)

请注意,handleClick 函数实例将在块内的每个函数调用上创建,因此每次调用的事件处理程序的地址将不同。因此,React 框架始终会看到事件处理程序发生了变化。在上面的示例中,React 会将 handleClick 视为每次调用的新值。它根本没有工具可以将其识别为同一个调用。

它的作用useCallback是,如果列出的变量没有更改,它会在内部存储函数的第一个引入版本并将其返回给调用者。

const Component = () => {
  const [counter, setCounter] = useState(0);

  const handleClick = useCallback(() => {
    setCounter(counter + 1);
  }, [])

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleClick}>+1</button>
  </div>
}
Run Code Online (Sandbox Code Playgroud)

现在,通过上面的代码,React 将handleClick通过 -function 调用将 -event 处理程序识别为相同的useCallback。它总是返回相同的函数实例,React 组件渲染机制会很高兴。

将函数存储在 will 内部useCallback会导致一个新问题。函数调用的存储实例将无法直接访问当前函数调用的变量。相反,它将看到在创建存储函数的初始闭包调用中引入的变量。因此该调用对于更新的变量不起作用。这就是为什么您需要告诉某些使用的变量是否已更改。这样 useCallback 就会将当前的函数调用实例存储为新的存储实例。作为第二个参数的变量列表useCallback列出了此功能的变量。在我们的示例中,我们需要告诉useCallback-function 我们需要在每次调用时使用新版本的 counter 变量。如果我们不这样做,调用后的计数器值将始终为 1,它来自原始值 0 加 1。

const Component = () => {
  const [counter, setCounter] = useState(0);

  const handleClick = useCallback(() => {
    setCounter(counter + 1);
  }, [counter])

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleClick}>+1</button>
  </div>
}
Run Code Online (Sandbox Code Playgroud)

现在我们有了代码的工作版本,不会在每次调用时重新渲染。

值得注意的是,useState这里的 -call 出于同样的原因。功能块没有内部状态,因此钩子使用useState,useCallbackuseMemo来模仿类的基本功能。从这个意义上说,函数式编程是历史上的一大倒退,更接近过程式编程。

useMemouseCallback与其他对象和变量的机制相同。有了它,您可以限制组件重新渲染的需要,因为useMemo如果列出的字段未更改,则该函数将在每次函数调用时返回相同的值。

新的 React hooks 方法的这一部分绝对是系统的最弱点。useCallback几乎违反直觉并且很容易出错。使用useCallback-calls 和依赖项,很容易最终陷入内部循环。我们在 React Class 方法中没有这个警告。

毕竟,最初的类方法更加有效。这useCallback将减少重新渲染的需要,但每次当函数的一些因变量发生变化时,它都会重新生成函数,并且如果变量发生变化,则匹配本身会产生开销。这可能会导致不必要的重新渲染。React 类的情况并非如此。


Bar*_*rbu 6

我举了一个小例子来帮助其他人更好地理解它的行为。您可以在此处运行演示或阅读下面的代码:

import React, { useState, useCallback, useMemo } from 'react';
import { render } from 'react-dom';

const App = () => {
    const [state, changeState] = useState({});
    const memoizedValue = useMemo(() => Math.random(), []);
    const memoizedCallback = useCallback(() => console.log(memoizedValue), []);
    const unMemoizedCallback = () => console.log(memoizedValue);
    const {prevMemoizedCallback, prevUnMemoizedCallback} = state;
    return (
      <>
        <p>Memoized value: {memoizedValue}</p>
        <p>New update {Math.random()}</p>
        <p>is prevMemoizedCallback === to memoizedCallback: { String(prevMemoizedCallback === memoizedCallback)}</p>
        <p>is prevUnMemoizedCallback === to unMemoizedCallback: { String(prevUnMemoizedCallback === unMemoizedCallback) }</p>
        <p><button onClick={memoizedCallback}>memoizedCallback</button></p>
        <p><button onClick={unMemoizedCallback}>unMemoizedCallback</button></p>
        <p><button onClick={() => changeState({ prevMemoizedCallback: memoizedCallback, prevUnMemoizedCallback: unMemoizedCallback })}>update State</button></p>
      </>
    );
};

render(<App />, document.getElementById('root'));
Run Code Online (Sandbox Code Playgroud)