📌 일기장 만들기 (10) - 최적화2 (컴포넌트 재사용) : React.memo
☑️ 컴포넌트 렌더링
- 컴포넌트 트리
부모 : App 컴포넌트
자식 : CountView 컴포넌트와 TextView 컴포넌트 - state : count와 text
- prop : count와 text
순서대로 코드를 수행하면서 어떤 컴포넌트들이 업데이트되는지?
1) setCount(10)이 실행
2) App 컴포넌트의 count state 변화
3) App 컴포넌트 state 업데이트되면서 리렌더링
4) 자식 컴포넌트인 CountView 또한 리렌더링
- 연산 낭비 : TextView는 text state가 바뀐게 아니므로 리렌더링될 필요가 없음 (App 컴포넌트가 업데이트되었다고 해도 TextView 컴포넌트가 받고 있는 Prop인 text가 바뀐 상황이 아니므로)
☑️ 연산 낭비를 막기 위해 함수형 컴포넌트에게 업데이트 조건을 걸자
- 자식 컴포넌트들에게 각각 업데이트 조건을 걸어서 우리의 성능을 지키자!
- CountView 업데이트 조건 : count가 변경될 때만 렌더링해라
- TextView 업데이트 조건 : text가 변경될 때만 렌더링해라
☑️ React.memo
- 리액트 공식문서를 활용해 알아보자 https://ko.reactjs.org/
- API 참고서 > React.Component
- 고차 컴포넌트(HOC) : 컴포넌트를 가져와서 새 컴포넌트를 반환하는 함수
- 가죽을 주면 구두를 만들어 주겠다 : 함수를 호출할 때 매개변수로 컴포넌트를 전달했더니 더 강화된 컴포넌트로 돌려주겠다
- React.memo는 고차 컴포넌트
- React.memo 라는 함수에 컴포넌트를 매개변수를 주면 뭔가 좋아진 컴포넌트를 MyComponent에 반환하게 된다
- 쉽게 풀이하자면 똑같은 Props를 받으면 똑같은 걸 내놓겠다는 뜻 = MyComponent 컴포넌트한테 똑같은 Props가 바뀐 것처럼 줘도 이거 똑같잖아하고 다시 컴포넌트를 계산안할래 = 리렌더링하지 않겠다는 뜻
- 정리해서, React.memo는 우리가 리렌더링하지 않았으면 하는 컴포넌트에 감싸주면 Props가 바뀌지 않으면 리렌더링하지 않은 강화된 컴포넌트로 돌려주겠다는 것 (다만, 자기자신의 state가 바뀌면 리렌더링은 됨)
☑️ 예제1
src/App.js
import OptimizeTest from "./OptimizeTest";
function App() {
return (
<div className="App">
<OptimizeTest />
</div>
);
}
src/OptimizeTest.js
- useEffect로 확인 : 콘솔 출력 시 누르거나 입력할 때마다 출력됨
- CountView를 눌렀을 때 Textview까지 렌더링 (반대도 같은 상황)
- React.memo 를 활용해 리렌더링이 일어나지 않도록 걸어주기
import React, { useState, useEffect } from "react";
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>
});
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;
☑️ 객체를 비교하는 방법
- 값도 같고 형도 같은데 왜 다르다고 나올까?
- 자바스크립트가 객체나 함수, 배열같은 비원시타입의 자료형을 비교할 때 값에 의한 비교가 아닌 주소에 의한 비교를 진행한다.
- 얉은 비교 : 객체의 주소에 의한 비교를 하기 때문에
- 각각 할당 시 각각의 주소를 갖기 때문에 다르다고 판단하게 됨
- 리액트 공식 문서 참조
- areEqual 함수 : props들이 서로 같으면 true 반환, props들이 서로 다르면 false 반환 (areEqual 함수에 의해 판단)
☑️ 예제2
src/OptimizeTest.js
import React, { useState, useEffect } from "react";
const CounterA = React.memo(({count}) => {
useEffect(()=>{
console.log(`CounterA Update - count: ${count}`);
});
return <div>{count}</div>
});
// CounterB도 처음엔 똑같이 React.memo로 감싸주었다가
// prop인 obj가 객체이기 때문에 (얇은 비교로 인해) 해제시킴
const CounterB = ({obj}) => {
useEffect(()=>{
console.log(`CounterB Update - count: ${obj.count}`);
});
return <div>{obj.count}</div>
};
// areEqual 함수 사용하여 props들 비교
const areEqual = (prevProps, nextProps) => {
// return true // 이전 Props와 현재 Props가 같다 > 리렌더링을 일으키지 않음
// return false // 이전과 현재가 다르다 > 리렌더링을 일으킴
if(prevProps.obj.count === nextProps.obj.count){ return true; }
return false;
};
// 매개변수로 CounterB 함수와 areEqual 함수 전달
// CounterB는 areEqual 함수의 판단에 따라 리렌더링을 할지 말지 결정
const MemoizedCounterB = React.memo(CounterB, areEqual);
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>
<MemoizedCounterB obj={obj} />
<button onClick={()=>setObj({count: obj.count})}>B Button</button> // 객체를 값으로 할당
</div>
</div>
);
};
export default OptimizeTest;
'React > React 공부' 카테고리의 다른 글
리액트 fiber 아키텍쳐, useState 내부 구조 (0) | 2023.07.28 |
---|---|
[세이멀스] PTJ 리팩토링 시작 : Heroku CI/CD를 곁들인 (0) | 2023.02.25 |
일기장 만들기 (9) - 최적화1 (연산결과 재사용) : useMemo (0) | 2023.02.11 |
React Developer Tools(RDT) 활용하기 (0) | 2023.02.11 |
일기장 만들기 (9) API 호출하기 : useEffect x fetch (0) | 2023.02.11 |