Blog/React.js

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

용디 2022. 2. 24. 00:20

🤷‍♀️ 컴포넌트 조건 업데이트가 필요한 이유?

지금까지는 부모 컴포넌트가 가지고 있는 데이터 A의 상태가 업데이트 되면 A를 받는 자식 뿐만 아니라 리렌더가 필요없는 자식까지 렌더링이 되었는데 이는 연산의 낭비 이다. 이러한 상황을 해결하기 위해서 React.memo를 활용해 함수형 컴포넌트에게 업데이트 조건을 걸어주면 업데이트 조건에 일치하는 컴포넌트만 리렌더링 하여 성능 최적화를 할 수 있다.


🤷‍♀️ React.memo 란?

React.memo는 고차 컴포넌트 이며, 고차 컴포넌트는 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수이다.

사용방법은 React.memo로 리렌더링을 원하지 않는 컴포넌트를 감싸주면 되는데, props가 바뀌지 않으면 리렌더링 하지 않는 강화된 컴포넌트를 돌려준다. 쉽게 말해 함수를 호출해 매게변수로 컴포넌트를 전달하면 더 좋아진 컴포넌트를 반환 해준다. (BUT 자기자신의 state가 바뀌면 리렌더링 된다.)

 

공식문서참고 : https://ko.reactjs.org/docs/react-api.html#reactmemo


👾 예시코드 (기초) 

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

const TextView = ({text}) => {
    // 👉 리렌더링시 props 확인하기 위해
    useEffect(() => {
       console.log(`Update :: Text : ${text}`);
    });
    return <div>{text}</div>;
};

const CountView = ({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>카운트</h2>
                <CountView count={count} />
                <button onClick={() => setCount(count + 1)}>+</button>
            </div>
            <div>
                <h2>텍스트</h2>
                <TextView text={text} />
                <input value={text} onChange={(e) => setText(e.target.value)} />
            </div>
        </div>
    );
};

export default OptimizeTest;

 

👾 예시코드 결과

➡️ Count만 눌렀을 뿐인데 불필요한 Text까지 같이 렌더되는걸 볼 수 있다.

 

👀 React.memo 적용

~~~~~
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>;
});
~~~~~

👀 React.memo 적용 결과

 

➡️ React.memo를 사용하니 Count만 눌렀을 때는 Count만 Text 입력했을때는 Text만 필요한 부분만 선택적으로 렌더 된다.


👾 예시코드 (응용)

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

const CounterA = React.memo(({count}) => {
    useEffect(() => {
        console.log(`A 업데이트 : count = ${count}`);
    });
    return <div>{count}</div>
});

const CounterB = React.memo(({obj}) => {
    useEffect(() => {
        console.log(`B 업데이트 : count = ${obj.count}`);
    });
    return <div>{obj.count}</div> // 👉 객체니까 점 표기법으로
});

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>
                <CounterB obj={obj}/>
                <button onClick={()=>setObj({
                    count: obj.count
                })}>B button</button>
            </div>
        </div>
    );
};

export default OptimizeTest;

 

👾 예시코드 결과 : 왜 B만 리렌더가 일어날까?

A, B 모두 React.memo를 이용했고, 돌아오는 값이 계속 1이기 때문에 업데이트가 되면 안되는데 B는 계속 업데이트가 된다. 이유는 B는 객체로 이루어져있고 객체를 비교할때는 얕은 비교를 하기때문에 해당 문제가 발생 된 것.


🤷‍♀️ 객체를 비교하는 방법?

🤔 값도 같고 형태도 같은데 왜 다를까?

자바스크립트는 객체, 함수, 배열 같은 비원시타입의 자료형을 비교할때 값에 의한 비교가 아닌 주소에 의한 비교를 하기 때문에 얕게 비교하여 다르다고 판단한다.

 

각각 다른 객체를 생성해서 할당하게 되면 해당 객체는 생성되자마자 고유한 메모리 주소를 갖게 되고, 자바스크립트는 이를 값이 아닌 주소로 비교하기 때문에 값이 똑같아도 서로 다르다라고 판단하는 것이다.

 

😮 그렇다면 반대로..

객체인 a를 b에 대입을 시켜도 주소값은 동일하기 때문에 결과는 둘은 같다고 판단한다.


 

🤷‍♀️ 얕은 비교인 객체도 값이 같으면 리렌더 되지 않게 하는 방법은?

const CounterB = ({obj}) => {
    useEffect(() => {
        console.log(`B 업데이트 : count = ${obj.count}`);
    });
    return <div>{obj.count}</div>
};

// 💡 React.memo의 비교함수로 적용된다.
const areEqual = (prevProps, nextProps) => {
    if(prevProps.obj.count === nextProps.obj.count) {
        return true; // 👉 return true; 이전 프롭스와 현재의 프롭스가 같다 => 리렌더링을 일으키지 않음
    }
    return false; // 👉 return false; 이전과 현재가 다르다 => 리렌더링을 일으킴
};

// 💡 위 코드 단축
// const areEqual = (prevProps, nextProps) => {
//     return prevProps.obj.count === nextProps.obj.count
// };

// 👉 CounterB는 areEqual의 판단에 따라서 리렌더링 여부가 결정되는 메모화된 컴포넌트가 되었다.
const MemoizedCounter = React.memo(CounterB,areEqual);

~~~~~

<div>
    <h2>Counter B</h2>
    <MemoizedCounter obj={obj}/>
    <button onClick={()=>setObj({
        count: obj.count
    })}>B button</button>
</div>

 

👀 결과..아..이퀄

(좌) 리액트 공식 문서 예제 코드


Demo : winterlood - codesandbox