일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- NextJS v13
- greedy
- 동전 0
- 항해99솔직후기
- 카테고리필터
- 클라이언트 컴포넌트
- db수정
- 항해99
- 서버 컴포넌트
- 중복선택
- JavaScript
- 자바스크립트
- 알고리즘
- jQuery
- 백준
- 항해99추천
- 탐욕알고리즘
- react
- 항해99후기
- 중복카테고리
- 부트캠프항해
- server component
- 그리디
- 로딩 후 실행
- 배열 중복 제거
- 날씨 api
- 숫자를 별점으로
- 프로그래머스
- 배열 메소드
- 실전프로젝트
- Today
- Total
공부 및 일상기록
[React] Memoization 본문
메모이제이션 (Memoization)
연산의 결과값을 메모리에 저장해 두고 이전값과 결과가 동일할 때, 재사용 하는 기법
React.memo
리액트 메모는 리액트의 고차컴포넌트(HOC)이다. 일반적인 컴포넌트를 넣으면 최적화된 컴포넌트를 제공해주는 역할을 수행한다. 최적화된 컴포넌트는 Prop Check를 통해 props에 변화가 있는지 없는지 체크 후 렌더링 여부를 판단하여 새로 렌더링 할지 다시 재사용 할지를 정한다.
허나 무분별하게 사용한다면 컴포넌트를 메모이징할때 렌더링된 결과를 어딘가의 메모리에 저장해야하기 때문에 메모리를 추가적으로 소비하게 되므로 무분별한 사용은 피해야 한다.
적합한 상황
1)컴포넌트가 같은 Props로 자주 렌더링 될 때
2)컴포넌트가 렌더링이 될때마다 복잡한 로직을 처리해야 할 때
React.memo는 오직 props변화에만 의존하는 최적화 방법이다.
만약 컴포넌트가 useState, useReducer, useContext와 같이 상태와 관련된 훅을 사용한다면 props의 변화가 없더라도 스테이트나 컨텍스트가 변할때마다 다시 렌더링이 될 것이다.
예제를 위해 아래 두개의 컴포넌트 (App.js, Child.jsx) 를 살펴보자
React.memo를 사용해보자
App.js
import React, { useState } from "react";
import Child from "./Child";
const App = () => {
const [parentAge, setParentAge] = useState(0);
const [childAge, setChildAge] = useState(0);
const incrementParentAge = () => {
setParentAge(parentAge + 1);
};
const incrementChildAge = () => {
setChildAge(childAge + 1);
};
console.log("부모컴포넌트 렌더링됨!");
return (
<div style={{ border: "2px solid navy", padding: "10px", margin: "30px" }}>
<h1>👪부모</h1>
<p>age: {parentAge}</p>
<button onClick={incrementParentAge}>부모 나이 증가</button>
<button onClick={incrementChildAge}>자식 나이 증가</button>
<Child name={"홍길동"} age={childAge} />
</div>
);
};
export default App;
Child.jsx
const Child = ({ name, age }) => {
console.log("자식 컴포넌트가 렌더링 됨!");
return (
<div style={{ border: "2px solid powderblue", padding: "10px" }}>
<h3>👶자녀</h3>
<p>name:{name}</p>
<p>age: {age}</p>
</div>
);
};
export default Child;
간단히 설명하면 부모 나이 증가를 누르면 부모의 나이가 증가하고, 자식 나이 증가를 누르면 자식의 나이가 증가하게 되는 구조이다. 또한 부모와 자식 컴포넌트에 console.log를 심어서 각 컴포넌트가 렌더링 되는것을 확인 할 예정이다.
먼저 예상을 해보자
부모 나이 증가를 누르면 해당 컴포넌트의 상태가 변하면서 부모 컴포넌트가 리렌더링 되고, 때문에 자식 컴포넌트도 같이 리렌더링이 될것이다. (따라서 콘솔에 두 컴포넌트가 리렌더링 된것이 확인 될것이다.)
자식 나이 증가를 누르면 똑같이 두 컴포넌트가 리렌더링 될것이다.
하지만 사실 부모나이 증가를 누를 때 자식 컴포넌트가 변하는 값은 하나도 없다. 여기서 자식 컴포넌트는 굳이 렌더링이 되어야 할까? 이런 의문이 든다면 메모이제이션을 해볼 수 있다. 사용법은 아래 코드와 같이 간단하다. 자식 컴포넌트의 export default에 memo를 감싸주는 것이다.
import { memo } from "react";
const Child = ({ name, age }) => {
console.log("자식 컴포넌트가 렌더링 됨!");
return (
<div style={{ border: "2px solid powderblue", padding: "10px" }}>
<h3>👶자녀</h3>
<p>name:{name}</p>
<p>age: {age}</p>
</div>
);
};
export default memo(Child);
이제 다시 한번 확인해 보자.
이처럼 자식 컴포넌트의 리렌더링이 방지되었다.
리액트메모는 Child컴포넌트를 인자로 받아서 최적화 된 Child컴포넌트를 반환한 것이다. 리액트 메모로 최적화된 컴포넌트는 Props Check를 통해 props가 변했는지 체크하고, props의 변화가 없다면 리렌더링을 시키지 않고 이전에 이미 렌더링된 컴포넌트의 결과를 재사용 하는 것이다.
useMemo와 React.memo 함께 사용해보기
App.js
import React, { useState } from "react";
import Child from "./Child";
const App = () => {
const [parentAge, setParentAge] = useState(0);
const incrementParentAge = () => {
setParentAge(parentAge + 1);
};
console.log("부모컴포넌트 렌더링됨!");
const name = {
lastName: "홍",
firstName: "길동",
};
return (
<div style={{ border: "2px solid navy", padding: "10px", margin: "30px" }}>
<h1>👪부모</h1>
<p>age: {parentAge}</p>
<button onClick={incrementParentAge}>부모 나이 증가</button>
<Child name={name} />
</div>
);
};
export default App;
Child.jsx
import { memo } from "react";
const Child = ({ name }) => {
console.log("자식 컴포넌트가 렌더링 됨!");
return (
<div style={{ border: "2px solid powderblue", padding: "10px" }}>
<h3>👶자녀</h3>
<p>성:{name.lastName}</p>
<p>이름:{name.firstName}</p>
</div>
);
};
export default memo(Child);
이번에는 처음부터 memo를 적용한 Child 컴포넌트를 넘겨본다. 예상하기로는 위와 같이 자식 컴포넌트는 리렌더링이 되지 않을것 같다. 과연 그런지 직접 눈으로 본다.
예상과 다르게 자식컴포넌트가 리렌더링 된다.
그 이유는 이번에 props로 넘겨준 name이 객체이기 때문이다. 객체는 값을 가지고 있는것이 아니라 메모리에 주소를 참조하는 방식이다.
부모 컴포넌트에 선언된 name은 리렌더링 되면서 App컴포넌트 내부의 변수들 또한 모두 초기화가 되고, 그 말인 즉슨 name 객체 또한 초기화가 되어 새로운 object가 만들어 지게 되고 그렇게 새로 만들어진 object와 이전 object는 각각 다른 메모리 주소에 저장이 된다. 그렇기 때문에 memo(Child)컴포넌트는 prop이 달라졌다고 받아들이게 되는 것이다.
여기서 useMemo를 통해서 object를 메모이제이션 해본다.
//name을 아래처럼 메모이제이션 한다.
const name = useMemo(() => {
return {
lastName: "홍",
firstName: "길동",
};
}, []);
useCallback과 React.memo 사용해보자
App.js
import React, { useState } from "react";
import Child from "./Child";
const App = () => {
const [parentAge, setParentAge] = useState(0);
const incrementParentAge = () => {
setParentAge(parentAge + 1);
};
console.log("부모컴포넌트 렌더링됨!");
const tellMe = () => {
console.log("길동아 사랑해");
};
return (
<div style={{ border: "2px solid navy", padding: "10px", margin: "30px" }}>
<h1>👪부모</h1>
<p>age: {parentAge}</p>
<button onClick={incrementParentAge}>부모 나이 증가</button>
<Child name={"홍길동"} tellMe={tellMe} />
</div>
);
};
export default App;
Child.jsx
import { memo } from "react";
const Child = ({ name, tellMe }) => {
console.log("자식 컴포넌트가 렌더링 됨!");
return (
<div style={{ border: "2px solid powderblue", padding: "10px" }}>
<h3>👶자녀</h3>
<p>이름:{name}</p>
<button onClick={tellMe}>엄마 나 사랑해?</button>
</div>
);
};
export default memo(Child);
그렇다면 이번엔 useCallback을 이용해서 메모이제이션을 해보자
//useCallback으로 메모이제이션
const tellMe = useCallback(() => {
console.log("길동아 사랑해");
}, []);
리액트 메모는 남용하지 않는것이 제일 중요하다. 무분별한 사용은 성능에 굉장히 안좋아진다. 메모리를 추가적으로 소비해야 하기 때문이다..! 리액트 메모는 오직 props check만을 통해서 리렌더링을 할지 말지 결정하므로 state나 context가 변할때는 아무 소용이 없다.
'개발 > React' 카테고리의 다른 글
[React] useMemo와 useCallback (feat React.memo와 차이점) (1) | 2023.01.17 |
---|---|
[React] Portal (0) | 2023.01.17 |
[React] React.Fragment (0) | 2023.01.17 |
[React] HOC에 대해 설명 (0) | 2023.01.17 |
[React] context API (0) | 2023.01.12 |