[Udemy] React 기본 - 일기장 만들기(10) 최적화2 (컴포넌트 재사용) : React.memo

productuidev·2022년 5월 3일
0

React Study

목록 보기
29/52
post-thumbnail

React 기본 (Project)

Udemy - 한입크기로 잘라 먹는 리액트


📌 일기장 만들기 (10) - 최적화2 (컴포넌트 재사용) : React.memo

☑️ 컴포넌트 렌더링

  • 컴포넌트 트리
    부모 : App 컴포넌트
    자식 : CountView 컴포넌트와 TextView 컴포넌트
  • state : count와 text
  • prop : count와 text

순서대로 코드를 수행하면서 어떤 컴포넌트들이 업데이트되는지?
1) setCount(10)이 실행
2) App 컴포넌트의 count state 변화
3) App 컴포넌트 state 업데이트되면서 리렌더링
4) 자식 컴포넌트인 CountView 또한 리렌더링

  • 연산 낭비 : TextView는 text state가 바뀐게 아니므로 리렌더링될 필요가 없음 (App 컴포넌트가 업데이트되었다고 해도 TextView 컴포넌트가 받고 있는 Prop인 text가 바뀐 상황이 아니므로)

☑️ 연산 낭비를 막기 위해 함수형 컴포넌트에게 업데이트 조건을 걸자

  • 자식 컴포넌트들에게 각각 업데이트 조건을 걸어서 우리의 성능을 지키자!
  • CountView 업데이트 조건 : count가 변경될 때만 렌더링해라
  • TextView 업데이트 조건 : text가 변경될 때만 렌더링해라

☑️ React.memo

  • API 참고서 > React.Component

  • 고차 컴포넌트(HOC) : 컴포넌트를 가져와서 새 컴포넌트를 반환하는 함수
  • 가죽을 주면 구두를 만들어 주겠다 : 함수를 호출할 때 매개변수로 컴포넌트를 전달했더니 더 강화된 컴포넌트로 돌려주겠다

  • React.memo는 고차 컴포넌트
  • 쉽게 풀이하자면 똑같은 Props를 받으면 똑같은 걸 내놓겠다는 뜻 = 컴포넌트한테 똑같은 Props가 바뀐 것처럼 줘도 이거 똑같잖아하고 다시 컴포넌트를 계산안할래 = 리렌더링하지 않겠다는 뜻
  • 정리해서, React.memo는 우리가 리렌더링하지 않았으면 하는 컴포넌트에 감싸주면 Props가 바뀌지 않으면 리렌더링하지 않은 강화된 컴포넌트로 돌려주겠다는 것 (다만, state가 바뀌면 리렌더링은 됨)

☑️ 예제1

src/App.js

import OptimizeTest from "./OptimizeTest";

function App() {
  return (
    <div className="App">
      <OptimizeTest />
    </div>
  );
}

src/OptimizeTest.js

  • useEffect로 확인 : 콘솔 출력 시 누르거나 입력할 때마다 출력됨
  • CountView를 눌렀을 때 Textview까지 렌더링 (반대도 같은 상황)
  • React.memo 를 활용해 리렌더링이 일어나지 않도록 걸어주기
import React, { useState, useEffect } from "react";

const Textview = React.memo(({text}) => {
    useEffect( ()=>{
      console.log(`Update :: Text : ${text}`);
    });
  
    return <div>{text}</div>
  }); 

const Countview = React.memo(({count}) => {
  useEffect( ()=>{
    console.log(`Update :: Count : ${count}`);
  });

  return <div>{count}</div>
});

const OptimizeTest = () => {
  const [count, setCount] = useState(1);
  const [text, setText] = useState("");

  return (
    <div style={{padding:50}}>
      <div>
        <h2>count</h2>
        <Countview count={count} />
        <button onClick={()=>setCount(count+1)}>+</button>
      </div>
      <div>
        <h2>text</h2>
        <Textview text={text} />
        <input value={text} onChange={(e)=>setText(e.target.value)} />
      </div>
    </div>
  );
};

export default OptimizeTest;

☑️ 객체를 비교하는 방법

  • 값도 같고 형도 같은데 왜 다르다고 나올까?
  • 얉은 비교 : 객체의 주소에 의한 비교를 하기 때문에
  • 각각 할당 시 각각의 주소를 갖기 때문에 다르다고 판단하게 됨

  • 리액트 공식 문서 참조
  • areEqual 함수 : props들이 서로 같으면 true 반환, props들이 서로 다르면 false 반환 (areEqual 함수에 의해 판단)

☑️ 예제2

src/OptimizeTest.js

import React, { useState, useEffect } from "react";

const CounterA = React.memo(({count}) => {
  useEffect(()=>{
    console.log(`CounterA Update - count: ${count}`);
  });

  return <div>{count}</div>
}); 

// CounterB도 처음엔 똑같이 React.memo로 감싸주었다가
// prop인 obj가 객체이기 때문에 (얇은 비교로 인해) 해제시킴
const CounterB = ({obj}) => {
  useEffect(()=>{
    console.log(`CounterB Update - count: ${obj.count}`);
  });
  
  return <div>{obj.count}</div>
}; 

// areEqual 함수 사용하여 props들 비교
const areEqual = (prevProps, nextProps) => {
  // return true // 이전 Props와 현재 Props가 같다 > 리렌더링을 일으키지 않음
  // return false // 이전과 현재가 다르다 > 리렌더링을 일으킴

  if(prevProps.obj.count === nextProps.obj.count){ return true; }
  return false;
};

// 매개변수로 CounterB 함수와 areEqual 함수 전달
// CounterB는 areEqual 함수의 판단에 따라 리렌더링을 할지 말지 결정
const MemoizedCounterB = React.memo(CounterB, areEqual);

const OptimizeTest = () => {
  const [count, setCount] = useState(1);
  const [obj, setObj] = useState({
    count: 1 // 초기값(객체로)
  });

  return (
    <div style={{padding:50}}>
      <div>
        <h2>Counter A</h2>
        <CounterA count={count} />
        <button onClick={()=>setCount(count)}>A Button</button>
      </div>
      <div>
        <h2>Counter B</h2>
        <MemoizedCounterB obj={obj} />
        <button onClick={()=>setObj({count: obj.count})}>B Button</button> // 객체를 값으로 할당
      </div>
    </div>
  );
};

export default OptimizeTest;

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글