React의 작동 원리 2 - 데이터 타입과 React.Memo, useCallback

김명성·2022년 5월 12일
3

REACT

목록 보기
27/32
post-custom-banner
import React from 'react'

// DemoOutput.js는 App.js의 자식 컴포넌트다.
const DemoOutput = (props) => {
  // DemoOutput은 부모로부터 state를 props를 통해 전달받는다.
  // DemoOutput 내에서 변경이 일어난다면 App Component 전체가 다시 재평가되는데,
  // 이는 DemoOutput의 state를 App Component가 관리하고 있기 때문이다.
  
  // 사실 props를 전달 받지 않아도, DemoOutput은 재실행되고, 재평가된다.
  // 그 이유는 부모컴포넌트에서 state 변경이 일어나 부모 컴포넌트가 재실행 되기 때문이다.
  
  
  // DemoOutput이 재실행된다고 해서 실제 DOM이 변경된다는 것이 아니다.
  // 풀어서 설명하자면, 컴포넌트 함수의 재실행, 재평가가 일어나더라도
  // 실제 DOM이 다시 렌더링되거나 변경되는 것은 아니라는 것이다.
  // 부모 컴포넌트가 재실행 되면, 모든 자식컴포넌트 역시 재실행, 재평가가 들어간다.
  // 따라서 DemoOutput 컴포넌트 뿐만 아니고, 그 외의 자식 컴포넌트도 모두 재평가 된다.
  
  console.log('DemoOutput Running');
  return <p>{props.show ? 'this is new' : ''}</p>
}


export default React.memo(DemoOutput)
// React.memo
// React.memo의 인자로 들어간 컴포넌트의 부모 컴포넌트에서 어떤 props를 전달 받았는지 확인하고,
// 전달 받은 props의 신규 값을 확인한 뒤 이를 기존의 props의 값과 비교한다.
// 그리고 props의 값이 바뀐 경우에만 컴포넌트를 재실행 및 재평가하게 된다.
// React.memo를 사용한다면 부모 컴포넌트가 변경되더라도,
// 자식 컴포넌트가 전달 받는 props값이 바뀌지 않았다면
// 해당 자식컴포넌트의 실행은 건너뛰게 된다.

// 그렇다면 모든 컴포넌트에 React.memo를 사용하면 되지 않을까?
// 아쉽지만 최적화 또한 비용이 따른다.
// memo 메서드는 부모 컴포넌트의 변경이 발생하면 인수로 전달된 컴포넌트로 이동하고,
// 기존 props 값과 새로운 값을 비교하게 되는데,
// 비교를 위해서 먼저 기존 props값을 저장할 공간과 비교하는 작업이 필요하다.
// 즉, memo를 사용하게 되면 저장 공간 확보와 비교하는 작업에
// 개별적인 성능 비용이 필요하다는 것이다.
// 따라서 성능 효율은 어떤 컴포넌트를 최적화하느냐에 따라 달라진다.

// React.memo를 사용한다는 것은
// 부모 컴포넌트로부터 자식 컴포넌트가 재실행,재평가되는 전파의 가지를 잘라내는 행위임을 기억하자.
import React from 'react';

import classes from './Button.module.css';



// 아래 버튼 컴포넌트를 보면 onClick이라는 프롭스와 children 밖에 없으며
// 모든 값은 불변하는 값이다.
// 텍스트도 같고 실행되는 함수도 항상 같지만, React.memo를 사용해도 재평가,재실행이 일어난다.
// 이것은 React에서 흔하게 발생하는 오류 중 하나다.
// 컴포넌트는 함수다. App 컴포넌트도 함수이기 때문에 일반 자바스크립트 함수처럼 재실행된다.
// 여기서 조금 다른 것은 컴포넌트 함수는 사용자가 아닌 리액트에 의해 호출된다는 것이다.
// 그렇지만 여전히 일반 함수처럼 실행되는데, 이말은 컴포넌트 내 모든 코드가 다시 실행된다는 뜻이다
const Button = (props) => {


  
  return (
    <button  
      type={props.type || 'button'}
      className={`${classes.button} ${props.className}`}
      onClick={props.onClick}
      disabled={props.disabled}
    >
      {props.children}
    </button>
  );
};



// App 컴포넌트에서 Button으로 전달하는 함수(props.onClick)는 매번 재생성된다.
// App 컴포넌트의 모든 렌더링, 모든 실행 사이클에서 완전히 새로운 함수로 생성된다. 재사용하지 않는다는 뜻이다.

// App함수가 재실행되고 재평가되어 컴포넌트 내 함수를 새로운 함수로 생성하기에
// 전달하는 값이 불변하는 값이더라도 새로 생성되어 전달되기 때문이다.
// 마지막 렌더링 사이클에서 false가 발생했더라도 재실행하면 새로운 false가 생성되는 것이다.
// 그렇다면, 똑같이 React.memo를 사용하였는데 DemoOutput은 동작하고, 왜 Button 컴포넌트는 동작하지 않는 것일까?

// 그것은 전달하려는 값의 데이터 타입에 따라 달라지기 때문이다.
// React.memo가 하는 일은 props의 값을 확인하고
// 재평가 직전의 props의 값인 props.previous.show를 일반 비교 연산자를 통해 비교한다.
// (props.show === props.previous.show)
// show의 값이 원시값이라면 위 예는 true를 반환하고, 재사용한다.
// 그러나 배열,객체,함수는 참조값일 때에는 previous값과 현재의 값을 같다고 평가하지 않는다.
// props.onClick과 props.previous.onClick을 예로 들면 함수는 참조값이기때문에 false이고, React.memo는 다른 값이라고 전달한다.

// 그렇다면 React.memo는 props를 통한 참조값 (객체,배열,함수)를 사용할 수 없는 것일까?
// 그렇지 않고 해결할 수 있는 방안이 useCallback이다.


export default Button;

React.memo를 돕는 useCallback

//App.js

// useCallback hook은 기본적으로 컴포넌트 실행 전반에 걸쳐 함수를 저장할 수 있게 하는 훅이다.

// React에게 이 함수를 저장할 것이고, 매번 실행때마다 이 함수를 재생성할 필요가 없다는 걸 알릴 수 있다.
// 동일한 함수 객체가 메모리의 동일한 위치에 저장되므로 이를 통해 비교 작업을 할 수 있게된다.
// 같은 메모리 안의 같은 위치를 가리키고 있다면 이 두 객체는 같은 객체로 간주한다.
// 이 것이 useCallback이 하는 일로, 선택한 함수를 리액트의 내부 저장 공간에 저장하여
// 함수 객체가 실행될 때마다 이를 재사용할 수 있게 만든다.

// useCallback의 첫번쨰 인자로 함수를 전달하면, 재실행 될 때마다 React가 저장된 함수를 찾아 재사용한다.
// useCallback의 두번째 인자는 의존성 배열로 useEffect의 의존성 배열과 같은 기능을 한다.

// 의존성 배열에 state,props,context를 지정하여 변화를 감지할 수 있게 만든다.
function App() {
  const toggleParagraphHandler = useCallback(() => {
    setShowParagraph(prev => !prev);
  },[]);
  ...
}
  
post-custom-banner

0개의 댓글