[React] 최적화 2. 컴포넌트 재 사용
📌 리액트 공식 문서 https://ko.reactjs.org/docs/getting-started.html -> API 참고서 -> react -> React.memo
🔮 React.memo의 역할
const MyComponent = React.memo(function MyComponent(props) {
/* props를 사용하여 렌더링 */
});
공식 문서에 나와 있는 예시인데, 설명을 읽어보면 컴포넌트(MyComponent)를 React.memo 함수의 매개변수로 전달하면 성능이 향상된 컴포넌트(고차 컴포넌트)로 반환 해준다.
-> 고차 컴포넌트가 된 MyComponent는 props가 변하지 않는다면 리랜더되지 않는다. (물론 props가 변하면 리랜더가 되겠지만,,,)
📐 OptimizeTest.js
이 컴포넌트는 그냥 React.memo()의 기능을 확인하기 위해서 만든 컴포넌트이다.
import { useState, useEffect } from "react";
const Countview = ({ count }) => {
useEffect(() => {
console.log(`Update :: count : ${count}`);
});
return <div>{count}</div>;
};
const Textview = ({ text }) => {
useEffect(() => {
console.log(`Update :: Text : ${text}`);
});
return <div>{text}</div>;
};
const OptimizeTest = () => {
const [count, setCount] = useState(1);
const [text, setText] = useState("");
return (
<div style=>
<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;
- OptimizeTest 컴포넌트 밑에
- Countview 컴포넌트와
- Textview 컴포넌트가 존재
Countview와 Textview 컴포넌트가 변경될 때마다 업데이트를 해주기 위해서 useEffect() 사용
❓ 문제점 ❓
근데 이렇게 되면 컴포넌트가 변경될 때마다 업데이트를 해주긴 하는데, 변경되지 않은 나머지 컴포넌트도 반드시 업데이트를 하게 된다.
💡 해결책 💡
React.memo() !
📐 React.memo() 사용 1
const Countview = React.memo(({ count }) => {
useEffect(() => {
console.log(`Update :: count : ${count}`);
});
return <div>{count}</div>;
});
const Textview = React.memo(({ text }) => {
useEffect(() => {
console.log(`Update :: Text : ${text}`);
});
return <div>{text}</div>;
});
-> 이렇게 되면 변경되지 않은 컴포넌트는 업데이트가 되지 않는 것을 확인할 수 있다.
📐 React.memo() 사용 2
두 번째 예시에서는 하나는 prop으로 그냥 숫자를, 다른 컴포넌트의 prop을 객체로 넣어보고 비교해본다.
const CounterA = React.memo(({ count }) => {
useEffect(() => {
console.log(`CounterA update :: count : ${count}`);
});
return <div>{count}</div>;
});
const CounterB = React.memo(({ obj }) => {
useEffect(() => {
console.log(`CounterB update :: obj.count : ${obj.count}`);
});
return <div>{obj.count}</div>;
});
지금 두 컴포넌트 모두 setCount()와 setObj()의 매개변수를 처음의 할당값인 1로 하고, 더이상 변화되지 않도록 count, obj.count만 전달한 상태 = 즉, 값이 변경되지 않는 상태라는 뜻
두 컴포넌트 모두 React.memo() 를 사용하였고, 값이 변경되지 않는다면 두 컴포넌트 모두 리랜더가 되지 않을 것 같다.
하지만 정수형을 전달했던 CounterA의 경우는 리랜더가 되지 않았지만, 객체를 전달했던 CounterB의 경우는 리랜더가 되고 있다. -> 왜 WHY 🖐️?
❗ 객체는 얕은 비교를 하기 때문 ❗
js는 객체, 함수, 배열은 주소를 통해서 비교하기 때문에 값이 같더라도 다른 주소이므로 다르다고 판단해서 onClick을 할 때마다 매번 리랜더가 되었던 것이다.
💡 해결책 💡
areEquel()을 사용
📍 areEquel()을 사용해서 깊은 비교해보기
areEquel() 같은 경우는
true를 반환하면 값이 같으므로 리랜더하지 말라는 뜻이고
false를 반환하면 값이 다르다는 의미이므로 리랜더하라는 뜻이다.
const areEquel = (prevProps, nextProps) => {
if (prevProps.obj.count === nextProps.obj.count) {
return true; // 이전 프롭스와 현재 프롭스가 같다 -> 리랜더링을 일으키지 않는다.
}
return false; // 이전 프롬스와 현재 프롭스가 같지 않다. -> 리랜더링을 해라
};
위 코드의 areEquel() 을 보게 되면 이전 객체의 count 값과 다음 객체의 count 값이 같으면(true) 리랜더하지 말고, 값이 다르면(false) 리랜더 하라는 의미이다. -> 이 함수의 리턴은 true/false
const MemoizedCounterB = React.memo(CounterB, areEquel);
이렇게 되면 컴포넌트는 CounterB가 아니라 MemoizedCounterB가 되므로 태그도 MemoizedCounterB로 변경 필요
댓글남기기