티스토리 뷰
개발냥이/자바스크립트(Javascript)
[리액트] 렌더링 최적화(React.memo, useMemo, useCallback) 정리
데브캣_DevCat 2023. 8. 2. 17:13리액트 컴포넌트의 렌더링 조건
- 컴포넌트는 자신의 state가 변경되거나 부모 컴포넌트가 렌더링 되었을 때 함께 렌더링이 됩니다.
const School = (props) => {
return (
<Student
name={'금동이'}
age={10}
gender={'male'}
/>
);
}
- Student 컴포넌트는 이름, 나이, 성별을 props로 전달받아서 Student의 정보를 보여주는 간단한 컴포넌트입니다.
const Student = ({ name, age, gender}) => {
return (
<div>
<h1>{name}</h1>
<span>{age}</span>
<span>{gender}</span>
</div>
);
}
- School 컴포넌트는 Student의 부모 컴포넌트입니다.
- 리액트는 기본적으로 부모 컴포넌트가 렌더링되면 모든 자식 컴포넌트도 같이 렌더링이 됩니다.
- Student 컴포넌트는 항상 같은 props를 전달받도록 되어있죠?! 그래서 Student는 항상 같은 결과를 화면에 보여줄 것입니다.
- 그런데 School 컴포넌트가 렌더링이 되면 Student 컴포넌트는 변화가 없음에도 함께 렌더링이 됩니다.
- 만약 Student 컴포넌트가 복잡한 로직이나 데이터베이스의 연산을 거쳐서 데이터를 가져온다고 하면 항상 같은 값을 리턴하는데 반복적으로 연산을 해야하니 성능적으로 매우 좋지 않을 것 같습니다.
- 그럼 Student 컴포넌트에 무언가 변화가 있어서(props가 변경이 될 때) 렌더링이 되어야만 할 때 렌더링이 되도록 할 수는 없을까요?!
React.memo
- React.memo는 리액트에서 제공하는 고차 컴포넌트중 하나입니다. 고차 컴포넌트(HOC)는 어떤 컴포넌트를 인자로 받아서 새로운 컴포넌트를 반환해주는 컴포넌트입니다.
- 렌더링이 되어야 할 상황에 놓일 때마다 Prop check를 통해 props에 변화가 있다면 렌더링을 하고 변화가 없다면 기존의 렌더링 된 내용을 재사용하게 됩니다.
const Student = ({ name, age, gender}) => {
return (
<div>
<h1>{name}</h1>
<span>{age}</span>
<span>{gender}</span>
</div>
);
}
export default React.memo(Student);
- React.memo를 사용하려면 위와 같이 컴포넌트를 memo로 감싸주기만 하면 됩니다!
React.memo 를 사용하기 좋은 상황
- 컴포넌트가 같은 props로 자주 렌더링이 될 때
- 컴포넌트가 렌더링이 될 때마다 복잡한 로직을 처리해야 할 때
memo = memoization으로 기존의 렌더링된 데이터를 재사용하기 위해 메모리 어딘가에 저장해두는 것이므로 무분별한 사용은 오히려 성능 저하를 불러올 수 있습니다!
deep comparison
const School = (props) => {
const name = {
lastName: '엄',
firstName: '금동'
}
return (
<Student
name={name}
age={10}
gender={'male'}
/>
);
}
- 예시를 조금 바꾸어보았습니다.
- 기존에 string으로 넣었던 name prop을 객체로 전달하는데 이 때에도 string이 객체로 변한 것 외에는 Student는 항상 같은 화면을 보여주게 될 것입니다.
- 그런데 위와 같은 상황에서 React.memo를 사용하고 부모 컴포넌트를 렌더링 해보면 예상과는 다르게 자식 컴포넌트까지 함께 렌더링이 됩니다.
- 그 이유는 React.memo는 기본적으로 얕은 비교(swallow comparison)로 props의 변화를 감지하는데 참조 변수는 주솟값이 같은지를 확인하겠죠!
- 하지만 부모 컴포넌트는 렌더링이 되면서 모든 변수와 함수를 초기화하므로 주솟값이 변하게 되고 React.memo는 props가 변했다고 판단해서 자식 컴포넌트를 렌더링시키는 것입니다.
const Student = ({ name, age, gender}) => {
const equalComparison = (prev, cur) => {
prev.name === cur.name;
}
return (
<div>
<h1>{name}</h1>
<span>{age}</span>
<span>{gender}</span>
</div>
);
}
export default React.memo(Student, equalComparison);
- deep comparison을 수행하도록 하려면 memo의 두 번째 인자로 비교 함수를 전달하면 됩니다.
- 리렌더링이 되기 전에 비교 함수를 거쳐서 비교 함수가 true를 반환하면 렌더링을 하지 않고 false를 반환할 때만 렌더링이 될 것입니다!
useMemo
- useMemo는 메모이제이션 된 값을 반환합니다.
- React.memo 또한 메모이제이션을 하지만 props를 비교하는 것과 차이가 있습니다.
- 우선 바로 위 React.memo가 객체prop를 얕은 비교를 해서 렌더링 했던 경우에 useMemo를 사용하면 값 자체를 비교하기 때문에 비교 함수를 두 번째 인자에 넣어준 것과 동일하게 동작할 수 있고
- 또한 하나의 컴포넌트에서 두 개 이상의 state가 있을 때도 활용할 수가 있습니다.
const School = (props) => {
const name = useMemo(() => {
return {
lastName: '엄',
firstName: '금동'
};
}, []);
return (
<Student
name={name}
age={10}
gender={'male'}
/>
);
}
useCallback
- useCallback은 메모이제이션된 함수를 반환합니다.
- 앞서 name 객체를 props로 넘겼을 때와 마찬가지로 함수 또한 참조 변수이기 때문에 부모 컴포넌트가 렌더링이 되면 새로운 주솟값을 갖게 되고 자식 컴포넌트에서는 props가 바뀌었다고 판단해서 함께 렌더링이 됩니다.
const School = (props) => {
const meow = useCallback(() => {
console.log("야옹");
}, []);
return (
<Student
name={'금동이'}
age={10}
gender={'male'}
meow={meow}
/>
);
}
- useMemo를 사용했을 때와 마찬가지로 useCallback으로 사용하고자 하는 함수를 감싸주어서 사용할 수 있습니다.
- 이렇게 되면 함수의 종속 변수가 변하지 않는 이상 굳이 함수를 새로 생성하지 않고 이전에 있던 참조 변수를 그대로 사용하여 리렌더링을 방지할 수 있습니다.
useCallback 예시
import React, { useState, useEffect } from 'react';
function Profile({ id }) {
const [data, setData] = useState("");
const fetchData = () =>
fetch(`url/${id}`)
.then(response => response.json())
.then(({ data }) => data)
useEffect(() => {
fetchData().then(data => setData(data))
}, [fetchData])
}
- 위에서 fetchData는 함수이기 때문에 컴포넌트가 렌더링이 될 때마다 새로운 참조값으로 변경이 됩니다. 함수가 변경되었기 때문에 매번 useEffect가 실행되어 다시 렌더링 되어 무한루프게 빠지게 됩니다.
import React, { useState, useEffect } from 'react';
function Profile({ id }) {
const [data, setData] = useState("");
const fetchData = useCallback(
() =>
fetch(`url/${id}`)
.then(response => response.json())
.then(({ data }) => data),
[id],
)
useEffect(() => {
fetchData().then(data => setData(data))
}, [fetchData])
// ...
}
- 이런 경우에 useCallback을 이용해서 props로 받는 id가 변하지 않는 한 함수의 참조값이 변하지 않도록 유지시킬 수 있습니다.
렌더링 최적화 결론
- 함수 또는 컴포넌트가 동일한 입력으로 여러 번 호출되는 경우 메모이제이션은 유용할 수 있습니다.
- 그러나 연산에 대한 결과가 자주 바뀌는 경우에는 메모이제이션은 쓸데없는 비교 연산과 메모리 낭비를 불러오겠죠?!
- 따라서 연산이나 함수 자체가 복잡해서 다시 계산하는 데 비용이 많이 드는 경우처럼 적절한 곳에서만 사용해야겠습니다!
관련 자료
반응형
'개발냥이 > 자바스크립트(Javascript)' 카테고리의 다른 글
[자바스크립트] CSS 라이브러리 비교해보기 (0) | 2023.06.16 |
---|---|
[자바스크립트] 패키지 매니저 정리(npm, yarn, yarn berry, pnpm) (1) | 2023.06.13 |
[자바스크립트] 비동기 통신_async/await (0) | 2023.05.02 |
[자바스크립트] 비동기 통신_프로미스(Promise) (0) | 2023.04.25 |
[React] 리덕스(Redux) 이해하기 (0) | 2023.04.24 |
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 타입스크립트
- dfs
- SQL
- 이분탐색
- Algorithm
- 형변환
- CS
- Comparator
- JPA
- 자바트리
- 자바스크립트
- DP
- Queue
- 스프링
- 자바dp
- Nest
- 스프링부트
- 리액트
- 정렬
- Spring
- BFS
- 해시맵
- 자바bfs
- 백준
- SQLD
- JavaScript
- java
- 알고리즘
- 프로그래머스
- 자바
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
29 | 30 | 31 |
글 보관함