부모 컴포넌트가 리랜더되면
자식컴포넌트 또한 리랜더되기때문에 count state를 변경하더라도
setCount시 CountView, TextView 모두 리랜더된다.
또한 setText시에도 모두 리랜더 되기때문에 연산자원 낭비가 생긴다.
자신과 관련없는 업데이트로 인해 성능문제가 생기기 때문에 이런 낭비를 막기위해
자식 컴포넌트에게 업데이트 조건을 준다
그럴 때 필요한게
React.memo
React.memo는 고차 컴포넌트이다.
컴포넌트가 동일한 props로 동일한 결과를 랜더링 해낸다면, React.memo를 호출하고 결과를 메모이징하도록 래핑
똑같은 prop으로 똑같은 결과를 낸다(리랜더링 하지않겠다)
React.memo는 props변화에만 영향을 준다. (자기자신의 state가 바뀔때만)
고차 컴포넌트(HOC, Highter Order Component)
컴포넌트 로직을 재사용하기위한 react기술. HOC는 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수.
(가죽을 주면 신발을 만들어 주겠다)
const EnhancedComponent = higherOrderComponent(WrappedComponent);
컴포넌트 재사용 코드 작성
const OptimizeTest = () => {
return <div style={{ padding: 50 }}></div>
}
export default OptimizeTest;
OptimizeTest 컴포넌트 생성 후
App 상단에 임포트
import { useState, useEffect } from "react";
const TextView = ({ text }) => {
return <div>{text}</div>
}
const CountView = ({ 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;
공간 만들어 준 후 useState를 import 해줬다. (useEffect도 쓸것)
count랑 text state를 변경 시킬 공간을 만들어줬다.
이전시간에 다운받은 React Developer Tools 덕분에 수정하는 블록의 상태변화를 볼 수 있다
리랜더 될 때 props 상태확인
const TextView = ({ text }) => {
console.log(`Update :: Text : ${text}`);
return <div>{text}</div>
}
const CountView = ({ count }) => {
console.log(`Update :: Count : ${count}`);
return <div>{count}</div>
}
현재는 하나의 state만 바뀌어도 두개 모두가 리랜더링 된다
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>
});
이제 바뀌는 state만 리랜더링 되는 모습을 확인 할 수 있다.
import React, { useState } from "react";
const OptimizeTest = () => {
const [count, setCount] = useState(1);
const [obj, setObj] = useState({
//객체안에 count:1
count: 1
})
return (
<div style={{ padding: 50 }}>
<div>
<h2>Counter A</h2>
<button onClick={() => setCount(count)}>A button</button>
</div>
</div>
)
}
export default OptimizeTest;
setCount로 상태변화를 일으켰지만 바뀌는 값은 count. (도돌이표 같은 느낌?)
import React, { useEffect, useState } from "react";
const CounterA = React.memo(({ count }) => {
useEffect(() => {
console.log(`Counter A update - count : ${count}`);
})
return <div>{count}</div>
})
const CounterB = React.memo(({ obj }) => {
useEffect(() => {
console.log(`Counter B update - objCount : ${obj.count}`);
})
return <div>{obj.count}</div>
})
const OptimizeTest = () => {
const [count, setCount] = useState(1);
const [obj, setObj] = useState({
//객체안에 count:1
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;
카운터 A와 B에 컴포넌트를 만들고 React.memo로 개별 state를 줬지만
CounterA 버튼을 눌러도 콘솔창에 변화가 없다.
이유는 state가 그대로 이기 때문 (count 값이 변화가 없다)
그러나 CounterB 버튼을 누르게되면?
변화가 생긴다. 이유는 counterB는 객체로 만들었기 때문이다.
객체는 얕은 비교를 하기때문
얕은비교?
객체의 값을 비교하는 것이 아닌 주소를 비교하기때문에 값이 같을지라도 다른값으로 인식하게된다.
이런식으로
function MyComponent(props) {
/* props를 사용하여 렌더링 */
}
function areEqual(prevProps, nextProps) {
/*
nextProps가 prevProps와 동일한 값을 가지면 true를 반환하고, 그렇지 않다면 false를 반환
*/
}
export default React.memo(MyComponent, areEqual);
공식문서에 따르면 깊은 비교를 하기위해서는 areEqual를 사용한다. 두번째 인자로 별도 비교인자를 제공하는 것
깊은비교 예제
const areEqual = (prevProps, nextProps) => {
if (prevProps.obj.count === nextProps.obj.count) {
return true;
}
return false;
}
const MemoizedCounterB = React.memo(CounterB, areEqual);
areEqual을 만든 후 MemoizedCounterB를 React.memo의 비교함수로서 동작
이제 카운터B를 눌러도 상태에 변화가 없기때문에 리랜더링이 없다
const areEqual = (prevProps, nextProps) => {
// if (prevProps.obj.count === nextProps.obj.count) {
// return true;
// }
// return false;
return prevProps.obj.count === nextProps.obj.count;
}
areEqual을 이런식으로 한줄로 줄여도 된다.
참고 :
- 한 입 크기로 잘라먹는 리액트
'개발 > Javascript' 카테고리의 다른 글
[React] 최적화3 - 컴포넌트 최적화 (0) | 2022.07.19 |
---|---|
[Javascript] 숫자 출력 함수 / 나머지 매개변수 (0) | 2022.07.19 |
[React] 최적화 - 연산 결과 재사용 (0) | 2022.07.13 |
[React] React Developer Tools (0) | 2022.07.12 |
[React] API 호출하기 (0) | 2022.07.09 |