티스토리 뷰

React.memo는 함수형 컴포넌트를 최적화할 수 있다. (클래스형에는 적용되지 않음)

 

사용방법은 아래와 같이 해당 컴포넌트를 react.memo로 wrap해주면 된다.

import React from 'react';

import MyParagraph from "./MyParagraph';

const DemoOutput = (props) => {
 console.log('demooutput Running');
 return <MyParagraph>{props.show ? "this is new!" : ""}</MyParagraph>
 };
 
 export default React.memo(DemoOutput); //리액트 메모를 원하는 해당 컴포넌트에 wrap한다.

 

 

😎memo의 역할은 무엇인가요? 

  • React.memo는 인자로 들어간 컴포넌트에 어떤 props가 입력되는지 확인하고 입력되는 모든 props의 신규 값을 확인한 뒤 이를 기존의 props의 값과 비교하도록 리액트에게 전달한다. 그리고 props의 값이 바뀐 경우에만 컴포넌트를 재실행 및 재평가하게 된다. 즉, 부모 컴포넌트가 변경되었지만 인자로 들어간 컴포넌트의 props값이 바뀌지 않는다면 해당 컴포넌트의 실행은 건너뛰게 된다.(그렇게 되면 해당 컴포넌트의 자식 컴포넌트도 렌더링되지 않겠죠?) -> 불필요한 재렌더링을 피할 수 있다! 최적화가 이루어지고 있다.

✅그렇다면 이 hook을 모든 컴포넌트에 적용하면 좋은게 아닌가요? 왜 모든 컴포넌트에 적용하지 않는걸까요?

  • 최적화에는 대가가 따르기 때문이다. 
  • memo메소드는 App에 변경이 발생할 때마다 해당 컴포넌트로 와서 이전 props값과 현재 props값을 비교하게 하는데 그렇기 때문에 리액트가 두 가지 작업을 할 수 있어야 한다. 우선 이전 props값을 저장할 공간이 필요하고 두 값을 비교하는 작업이 필요하다. 그렇기 때문에 해당 프로젝트가 크거나 프로젝트가 커서 트리의 상위쪽에 존재하는 컴포넌트(자식 컴포가 많은 경우)가 아니라면 굳이 memo를 추가하는 작업이 필요하지 않게 된다.

✅하드코딩된 값(고정값)을 props로 내려주는 컴포넌트에 memo를 적용하면 재렌더링이 되지 않겠네요?

  • React.memo는 props를 통한 객체나 배열 또는 함수(참조형)를 가져오는 컴포넌트에 적용했을 때 유감스럽게도 재렌더링이 됩니다.
  • 그 이유는 자바스크립트가 참조형 값에 대해서는 형태가 같더라도 다른 값이라고 인식하기 때문입니다.
  • e.g.) let obj1 = {}, let obj2 = {} , obj1 === obj2 // false   그렇기 때문에 memo는 값이 변경되었다고 인식하여 재렌더링을 합니다. 

 React.memo는 props를 통한 객체나 배열 함수를 가져오는 컴포넌트에는 사용할 수 없나요?  

  • 다른 훅을 사용하면 memo를 원하는 방식대로 사용할 수 있다. 
  • 해결방법: 객체를 생성하고 저장하는 방식만 변경해주면 된다. -> 추가적인 리액트 훅이 필요하다. useCallback Hook을 사용하면 된다.

 

😎useCallback이란? 

- 컴포넌트 실행 전반에 걸쳐 함수를 저장할 수 있게 하는 훅으로 리액트에게 우리는 이 함수를 저장할 것이고 매번 실행할 때마다 이 함수를 재생성할 필요가 없다는 것을 알릴 수 있다. 이렇게되면 동일한 함수 객체가 메모리의 동일한 위치에 저장되므로 이를 통한 비교 작업을 할 수 있다. 

let obj1 = {};
let obj2 = {};
obj1 === obj2; // false
obj2 = obj1; //같은 공간을 가리키도록 할당
obj1 === obj2; //true

라는 위의 예시에서 obj2 === obj1 라는 작업을 통해 같은 메모리 안의 같은 위치를 가리키게 한다면  obj1 ===obj2 // true로 두 객체는 같은 객체로 간주한다. 이것이 바로 useCallback이 하는 일이다. 

 

우리가 선택한 함수를 리액트의 내부 저장 공간에 저장해서 함수 객체가 실행될 때마다 이를 재사용할 수 있도록 한다. 

useCallback의 사용방법은 memo와 같이 쉽다. 저장하려는 함수를 래핑하면 된다. 

toggleParagraphHandler 라는 함수를 wrapping하고 싶다면 아래와 같이해주면된다. 

const toggleParagraphHandler = useCallback(()=>{
 setShowPharagraph((prevShowParagraph)=>!prevShowParagraph);
}, []);
  • useCallback함수는 첫번째 인자로 래핑하려는 함수를 첫번째 인자로 전달하면 이 저장된 함수를 반환해준다. 그리고 이 App함수가 다시 실행되면 useCallback이  리액트가 저장한 함수를 찾아서 그 같은 함수 객체를 재사용한다. 따라서 어떤 함수가 절대 변하면 안된다면 useCallback을 사용하여 함수를 저장하면 된다.
  • useCallback은 두번째 인자로 useEffect처럼 의존성 배열을 전달하는데 이 배열은 무조건 있어야 하고 useCallback으로 감싼 컴포넌트로부터 어떤 값을 넣어도 사용 가능하다. 예를 들어 state나 props 컨텍스트를 지정할 수 있다. 그렇지만 굳이 의존성 배열에 값을 넣어주지 않아도 된다. (사실 이해가 잘 안가서 우선은 빈배열인 상태여도 오케이라고 이해함;) 

그렇다면 useCallback의 의존성 배열에는 뭘 넣어야 하는지..? 

  • 내 함수는 모든 재렌더링 주기마다 항상 똑같은 로직을 쓰는데 의존성 배열은 왜 필요함..?이라는 의문에 대한 해답 
  • 해당 질문에 대해서는 클로저에 대한 개념이 필요하다. (전에 배웠을 때도 어려웠는데 지금은 더 모르겠음..)
  • 해당 질문에 대한 대답은 강의를 다시 보고 정리하겠습니다. 
  • https://ko.javascript.info/closure
 

변수의 유효범위와 클로저

 

ko.javascript.info

 

댓글