>  기사  >  웹 프론트엔드  >  Memoization을 사용하여 React 성능을 향상시키는 방법에 대해 이야기해 보겠습니다.

Memoization을 사용하여 React 성능을 향상시키는 방법에 대해 이야기해 보겠습니다.

青灯夜游
青灯夜游앞으로
2022-03-29 20:02:102291검색

이 기사에서는 Memoization을 이해하고, Memoization이 필요한 이유를 소개하고, 성능 향상을 위해 React에서 Memoization을 구현하는 방법을 소개합니다. 모두에게 도움이 되기를 바랍니다.

Memoization을 사용하여 React 성능을 향상시키는 방법에 대해 이야기해 보겠습니다.

이 튜토리얼에서는 React에서 Memoization을 구현하는 방법을 알아봅니다. 메모화는 함수 호출 결과를 캐시하고 다시 필요할 때 캐시된 결과를 반환함으로써 성능을 향상시킵니다.

다음 내용을 다룰 것입니다:

  • React는 어떻게 뷰를 렌더링하나요?
  • 왜 메모가 필요한가요?
  • 함수 구성요소와 클래스 구성요소에 메모를 구현하는 방법은 무엇인가요?
  • Notes

이 글에서는 여러분이 React의 클래스와 함수 구성 요소에 대한 기본적인 이해가 있다고 가정합니다.

이 주제를 확인하려면 공식 React 문서comComponents and Props

https://reactjs.org/docs/comComponents-and-props.html

Memoization을 사용하여 React 성능을 향상시키는 방법에 대해 이야기해 보겠습니다.

을 확인하세요. React는 어떻게 뷰를 렌더링하나요?

React의 Memoization에 대한 자세한 내용을 논의하기 전에 먼저 React가 가상 DOM을 사용하여 UI를 렌더링하는 방법을 살펴보겠습니다. [관련 권장사항: Redis 동영상 튜토리얼]

Regular DOM은 기본적으로 트리 형태로 저장된 노드 집합을 포함합니다. DOM의 각 노드는 UI 요소를 나타냅니다. 애플리케이션에서 상태 변경이 발생할 때마다 해당 UI 요소와 모든 하위 요소에 해당하는 노드가 DOM 트리에서 업데이트되고 UI 다시 그리기가 트리거됩니다.

효율적인 DOM 트리 알고리즘의 도움으로 노드 업데이트가 더 빨라지지만 다시 그리기는 느리고 DOM에 UI 요소가 많을 때 성능에 영향을 미칠 수 있습니다. 따라서 React에는 가상 DOM이 도입되었습니다.

이것은 실제 DOM의 가상 표현입니다. 이제 애플리케이션 상태에 변화가 있을 때마다 React는 실제 DOM을 직접 업데이트하지 않고 새로운 가상 DOM을 생성합니다. 그런 다음 React는 이 새로운 가상 DOM을 이전에 생성된 가상 DOM과 비교하고 차이점을 찾은 다음(번역자 주: 업데이트해야 하는 노드를 찾습니다) 다시 그립니다.

이러한 차이점을 바탕으로 가상 DOM은 실제 DOM을 보다 효율적으로 업데이트할 수 있습니다. 이는 가상 DOM이 단순히 UI 요소와 모든 하위 요소를 업데이트하는 것이 아니라 실제 DOM에서 필요하고 최소한의 변경 사항만 효과적으로 업데이트하기 때문에 성능을 향상시킵니다.

왜 메모가 필요한가요?

이전 섹션에서는 React가 가상 DOM을 사용하여 DOM 업데이트 작업을 효율적으로 수행하여 성능을 향상시키는 방법을 살펴보았습니다. 이 섹션에서는 성능을 더욱 향상시키기 위해 Memoization을 사용해야 하는 이유를 설명하는 예를 제시합니다.

count라는 변수를 증가시키는 버튼이 포함된 상위 클래스를 생성하겠습니다. 또한 상위 구성 요소는 하위 구성 요소를 호출하고 매개 변수를 전달합니다. 또한 render 메서드에 console.log() 문을 추가했습니다. count 的变量。父组件还调用了子组件,并向其传递参数。我们还在 render 方法中添加了 console.log() 语句:

//Parent.js
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = () => {
    this.setState((prevState) => {
      return { count: prevState.count + 1 };
    });
  };

  render() {
    console.log("Parent render");
    return (
      <div className="App">
        <button onClick={this.handleClick}>Increment</button>
        <h2>{this.state.count}</h2>
        <Child name={"joe"} />
      </div>
    );
  }
}

export default Parent;

此示例的完整代码可在 CodeSandbox 上查看。

我们将创建一个 Child 类,该类接受父组件传递的参数并将其显示在 UI 中:

//Child.js
class Child extends React.Component {
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

每当我们点击父组件中的按钮时,count 值都会更改。由于 state 变化了,因此父组件的 render 方法被执行了。

传递给子组件的参数在每次父组件重新渲染时都没有改变,因此子组件不应重新渲染。然而,当我们运行上面的代码并继续递增 count

Parent render
Child render
Parent render
Child render
Parent render
Child render

이 예제의 전체 코드는 CodeSandbox

. 부모 구성 요소가 전달한 매개 변수를 받아들이고 이를 UI에 표시하는 Child 클래스를 만듭니다.

//Child.js
class Child extends React.PureComponent { // 这里我们把 React.Component 改成了 React.PureComponent
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

부모 구성 요소에서 버튼을 클릭할 때마다 count code> 값이 변경됩니다. 상태가 변경되었으므로 상위 구성 요소의 <code>render 메서드가 실행됩니다.

자식 구성 요소에 전달된 매개 변수는 상위 구성 요소가 다시 렌더링될 때마다 변경되지 않으므로 하위 구성 요소를 다시 렌더링해서는 안 됩니다. 그러나 위의 코드를 실행하고 count를 계속 증가시키면 다음과 같은 출력을 얻습니다.

Parent render
Child render
Parent render
Parent render
🎜 이 🎜sandbox🎜에서 위의 예를 경험하고 콘솔 출력을 볼 수 있습니다. 🎜🎜출력에서 상위 구성 요소가 다시 렌더링되면 하위 구성 요소에 전달된 매개 변수가 변경되지 않은 경우에도 하위 구성 요소가 다시 렌더링되는 것을 확인할 수 있습니다. 이로 인해 하위 구성 요소의 가상 DOM이 이전 가상 DOM과 비교 검사를 수행하게 됩니다. 하위 구성 요소에는 변경 사항이 없고 다시 렌더링 시 모든 props는 변경되지 않으므로 실제 DOM은 업데이트되지 않습니다. 🎜🎜실제 DOM이 불필요하게 업데이트되지 않는 점이 성능면에서는 정말 좋지만, 하위 컴포넌트에 실제 변경 사항이 없더라도 새로운 가상 DOM이 생성되고 차이점 검사가 수행되는 것을 확인할 수 있습니다. 작은 React 구성 요소의 경우 이 성능 비용은 미미하지만 대형 구성 요소의 경우 성능에 미치는 영향이 클 수 있습니다. 가상 DOM의 이러한 재렌더링 및 차이점 확인을 방지하기 위해 Memoization을 사용합니다. 🎜

React 中的 Memoization

在 React 应用的上下文中,Memoization 是一种手段,每当父组件重新渲染时,子组件仅在它所依赖的 props 发生变化时才会重新渲染。如果子组件所依赖的 props 中没有更改,则它不会执行 render 方法,并将返回缓存的结果。由于渲染方法未执行,因此不会有虚拟 DOM 创建和差异检查,从而实现性能的提升。

现在,让我们看看如何在类和函数组件中实现 Memoization,以避免这种不必要的重新渲染。

类组件实现 Memoization

为了在类组件中实现 Memoization,我们将使用 React.PureComponentReact.PureComponent 实现了 shouldComponentUpdate(),它对 stateprops 进行了浅比较,并且仅在 props 或 state 发生更改时才重新渲染 React 组件。

将子组件更改为如下所示的代码:

//Child.js
class Child extends React.PureComponent { // 这里我们把 React.Component 改成了 React.PureComponent
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

此示例的完整代码显示在这个 sandbox 中。

父组件保持不变。现在,当我们在父组件中增加 count 时,控制台中的输出如下所示:

Parent render
Child render
Parent render
Parent render

对于首次渲染,它同时调用父组件和子组件的 render 方法。

对于每次增加 count 后的重新渲染,仅调用父组件的 render 函数。子组件不会重新渲染。

函数组件实现 Memoization

为了在函数组件中实现 Memoization,我们将使用 React.memo()React.memo() 是一个高阶组件(HOC),它执行与 PureComponent 类似的工作,来避免不必要的重新渲染。

以下是函数组件的代码:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child); // 这里我们给子组件添加 HOC 实现 Memoization

同时还将父组件转换为了函数组件,如下所示:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  console.log("Parent render");
  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} />
    </div>
  );
}

此示例的完整代码可以在这个 sandbox 中看到。

现在,当我们递增父组件中的 count 时,以下内容将输出到控制台:

Parent render
Child render
Parent render
Parent render
Parent render

React.memo() 存在的问题

在上面的示例中,我们看到,当我们对子组件使用 React.memo() HOC 时,子组件没有重新渲染,即使父组件重新渲染了。

但是,需要注意的一个小问题是,如果我们将函数作为参数传递给子组件,即使在使用 React.memo() 之后,子组件也会重新渲染。让我们看一个这样的例子。

我们将更改父组件,如下所示。在这里,我们添加了一个处理函数,并作为参数传递给子组件:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = () => {
    console.log("handler");    // 这里的 handler 函数将会被传递给子组件
  };

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

子组件代码将保持原样。我们不会在子组件中使用父组件传递来的函数:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child);

现在,当我们递增父组件中的 count 时,它会重新渲染并同时重新渲染子组件,即使传递的参数中没有更改。

那么,是什么原因导致子组件重新渲染的呢?答案是,每次父组件重新渲染时,都会创建一个新的 handler 函数并将其传递给子组件。现在,由于每次重新渲染时都会重新创建 handle 函数,因此子组件在对 props 进行浅比较时会发现 handler 引用已更改,并重新渲染子组件。

接下来,我们将介绍如何解决此问题。

通过 useCallback() 来避免更多的重复渲染

导致子组件重新渲染的主要问题是重新创建了 handler 函数,这更改了传递给子组件的引用。因此,我们需要有一种方法来避免这种重复创建。如果未重新创建 handler 函数,则对 handler 函数的引用不会更改,因此子组件不会重新渲染。

为了避免每次渲染父组件时都重新创建函数,我们将使用一个名为 useCallback() 的 React Hook。Hooks 是在 React 16 中引入的。要了解有关 Hooks 的更多信息,你可以查看 React 的官方 hooks 文档,或者查看 `React Hooks: How to Get Started & Build Your Own"。

useCallback() 钩子传入两个参数:回调函数和依赖项列表。

以下是 useCallback() 示例:

const handleClick = useCallback(() => {
  //Do something
}, [x,y]);

在这里,useCallback() 被添加到 handleClick() 函数中。第二个参数 [x, y] 可以是空数组、单个依赖项或依赖项列表。每当第二个参数中提到的任何依赖项发生更改时,才会重新创建 handleClick() 函数。

如果 useCallback() 中提到的依赖项没有更改,则返回作为第一个参数提及的回调函数的 Memoization 版本。我们将更改父组件,以便对传递给子组件的处理程序使用 useCallback() 钩子:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = useCallback(() => { // 给 handler 函数使用 useCallback()
    console.log("handler");
  }, []);

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

子组件代码将保持原样。

此示例的完整代码这个 sandbox 中。

当我们在上述代码的父组件中增加 count 时,我们可以看到以下输出:

Parent render
Child render
Parent render
Parent render
Parent render

由于我们对父组件中的 handler 使用了 useCallback() 钩子,因此每次父组件重新渲染时,都不会重新创建 handler 函数,并且会将 handler 的 Memoization 版本传递到子组件。子组件将进行浅比较,并注意到 handler 函数的引用没有更改,因此它不会调用 render 方法。

值得注意的事

Memoization 是一种很好的手段,可以避免在组件的 state 或 props 没有改变时对组件进行不必要的重新渲染,从而提高 React 应用的性能。你可能会考虑为所有组件添加 Memoization,但这并不一定是构建高性能 React 组件的方法。只有在组件出现以下情况时,才应使用 Memoization:

  • 固定的输入有固定的输出时
  • 具有较多 UI 元素,虚拟 DOM 检查将影响性能
  • 多次传递相同的参数

总结

在本教程中,我们理解了:

  • React 是如何渲染 UI 的
  • 为什么需要 Memoization
  • 如何在 React 中通过函数组件的 React.memo() 和类组件的 React.PureComponent 实现 Memoization
  • 通过一个例子展示,即使在使用 React.memo() 之后,子组件也会重新渲染
  • 如何使用 useCallback() 钩子来避免在函数作为 props 传递给子组件时产生重新渲染的问题

希望这篇 React Memoization 的介绍对你有帮助!

原文地址:https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/

原文作者:Nida Khan

更多编程相关知识,请访问:编程视频!!

위 내용은 Memoization을 사용하여 React 성능을 향상시키는 방법에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제