이 글은 모던 자바스크립트로 배우는 리액트 입문 책을 공부하면서 샘플을 만들고, 느낀점을 추가해 작성하였습니다. React 프레임워크 기반의 프론트엔드 개발에 필요한 전반적인 내용을 빠르게 다루기 때문에 React 입문 개발자분들께 이 책을 추천합니다.
복잡한 어플리케이션의 경우 화면의 성능을 개선해야 하는 경우가 있다. React에서 어떻게 재랜더링이 발생하고, 개선할 수 있는지 알아보자.
재랜더링이 일어나는 경우
- state 값 변경
- prop이 변경된 컴포넌트
- 재랜더링된 컴포넌트 하위 모든 컴포넌트
개선 방법
React에서 제공하는 메모이제이션 방법을 통해서 성능 개선을 진행한다. 메모이제이션은 이전 처리
결과를 저장하여 처리 속도를 높이는 방법을 말한다.
예제 소스
- Parent 컴포넌트가 Child 컴포넌트를 포함하고 있고, 재랜더링 내용은 브라우저에 콘솔 로그(console.log)로 확인하자.
- “증가” 버튼을 클릭하면 Parent에 count 상태값이 변경되면서 Parent 재랜더링을 수행한다.
- Parent 하위에 있는 모든 하위 컴포넌트가 재랜더링을 수행하는 것이 확인가능하다.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86# Parent.jsx
import { useState } from 'react';
import { styled } from '@mui/material';
export default function Parent() {
console.log('Parent render');
const [count, setCount] = useState(0);
const onClickAddCount = () => {
console.log('증가 버튼 클릭 !');
setCount(count + 1);
};
return (
<div>
<div>{count}</div>
<Button onClick={onClickAddCount}>증가</Button>
<Child1 />
<Child4 />
</div>
);
}
const Child1 = () => {
console.log('Child1 render');
return (
<DivContainer color="red" margin="20px">
<p>Child1</p>
<Child2 />
<Child3 />
</DivContainer>
);
};
const Child2 = () => {
console.log('Child2 render');
return (
<DivContainer color="green" margin="10px">
<p>Child2</p>
</DivContainer>
);
};
const Child3 = () => {
console.log('Child3 render');
return (
<DivContainer color="blue" margin="10px">
<p>Child3</p>
</DivContainer>
);
};
const Child4 = () => {
console.log('Child4 render');
return (
<DivContainer margin="20px">
<p>Child4</p>
</DivContainer>
);
};
const Button = styled('button')`
border: 1px solid;
`;
const DivContainer = styled('div')`
border: 1px solid
${(props) => {
switch (props.color) {
case 'red':
return 'red';
case 'blue':
return 'blue';
case 'green':
return 'green';
default:
return 'black';
}
}};
margin: ${(props) => props.margin};
`;
1. 컴포넌트 메모이제이션 (memo)
컴포넌트의 상태가 변경되는 경우, 재랜더링된 컴포넌트 하위의 모든 컴포넌트가 재랜더링 되어 성능을 저하시킨다.
하위 컴포넌트 중 화면이 변하지 않아서 재랜더링하지 않아도 된다면, 컴포넌트를 memo 함수로 감싸고, 해당 컴포넌트는 props이 변경되었을때만 재랜더링을 수행한다.
- React.memo 함수로 기존 컴포넌트 함수를 감싸면 된다.
- 화면이 복잡하고, 많은 컴포넌트를 사용하여 재렌더링 비용이 큰 경우 적합하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18...
# 나머지 소스는 동일하다.
const Child1 = memo(() => {
console.log('Child1 render');
return (
<DivContainer color="red" margin="20px">
<p>Child1</p>
<Child2 />
<Child3 />
</DivContainer>
);
});
const Child2 = memo(() => {...});
const Child3 = memo(() => {...});
const Child4 = memo(() => {...});
...
2. 함수 메모이제이션 (useCallback)
Child1.jsx 컴포넌트에 “초기화” 버튼 추가하고, “초기화”버튼 클릭시 수행할 핸들러를 부모 컴포넌트(Parent.jsx)에서 prop으로 전달 받는다.
- 함수를 props으로 전달할 때, 컴포넌트 메모이제이션을 하더라도 재랜더링이 되면서 함수가 다시 생성되기 때문에, 하위 컴포넌트에서는 재랜더링이 발생한다.
- prop 값(함수 재선언) 변경에 따른 컴포넌트 재렌더링
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
32
33
34
35
36
37
38
39...
# 나머지는 기존 소스와 동일
export default function Parent() {
console.log('Parent render');
const [count, setCount] = useState(0);
const onClickAddCount = () => {
console.log('증가 버튼 클릭 !');
setCount(count + 1);
};
const onClickReset = () => {
console.log('초기화 버튼 클릭 !');
setCount(0);
}
return (
<div>
<div>{count}</div>
<Button onClick={onClickAddCount}>증가</Button>
<Child1 onClickReset={onClickReset} />
<Child4 />
</div>
);
}
const Child1 = memo(({ onClickReset }) => {
console.log('Child1 render');
return (
<DivContainer color="red" margin="20px">
<p>Child1</p>
<Button onClick={onClickReset}>초기화</Button>
<Child2 />
<Child3 />
</DivContainer>
);
});
...
화면 시나리오
- “증가” 버튼 클릭 시, Parent.jsx 재렌더링 -> onClickReset 함수 재선언
- Child1.jsx 컴포넌트에 prop 값(onClickReset 함수) 변경에 따른 Child1.jsx 컴포넌트 재랜더링
- “초기화” 버튼 클릭 시, Parent.jsx 재랜더링
- Child1.jsx 컴포넌트 props 값(onClickReset 함수) 변경에 따른 Child1.jsx 컴포넌트 재랜더링
useCallback 함수를 사용하여 함수 메모이제이션이 가능하다.
함수 안에서 사용하는 변수를 의존성 배열안에서 설정해야 한다.
- 의존성 배열이 비어 있는 경우: 컴포넌트가 최초 렌더링 되는 시점에 선언된 것을 재사용한다.
- 의존성 배열에 값이 있는 경우: 값이 변경되는 시점에 다시 작성된다.
1
2
3# useCallback 함수 형식: 첫번째 인수에 함수, 두번째 인수에 의존성 배열
const onClickReset = useCallback(() => setCount(0), []);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# 나머지는 기존 소스와 동일
# onClickReset 함수에 useCallback 함수로 감싸서 함수 메모이제이션 수행
export default function Parent() {
console.log('Parent render');
const [count, setCount] = useState(0);
const onClickAddCount = () => {
console.log('증가 버튼 클릭 !');
setCount(count + 1);
};
const onClickReset = useCallback(() => {
console.log('초기화 버튼 클릭 !');
setCount(0);
}, []);
return (
<div>
<div>{count}</div>
<Button onClick={onClickAddCount}>증가</Button>
<Child1 onClickReset={onClickReset} />
<Child4 />
</div>
);
}
3. 변수 메모이제이션 (useMemo)
React.useMemo를 사용하여 변수 메모이제이션을 통해서, 불필요한 재렌더링을 해결할 수 있다.
화면 시나리오
- Parent 컴포넌트가 재랜더링이 수행되면 profileName 선언이 다시 된다.
- profileName을 prop으로 사용하는 하위 컴포넌트가 재렌더링 된다.
이런 경우에 변수 메모이제이션 통해서 profileName 변수를 사용하는 컴포넌트의 불필요한 재렌더링이 발생한다.
- 로그인한 사용자의 세션 정보로부터 프로필명을 조회한다. -> 프로필명 = ‘이름 (부서명)’
- 프로필명의 경우 사용자 로그인 이후에 바뀌지 않을 정보이다.
1
2
3
4
5
6
7const sessionUser = getSessionUser();
# useMemo 함수 형식: 첫번째 인수에 변수가 값을 리턴하는 함수, 두번째 인수에 의존성 배열
const profileName = useMemo(() => {
const { name, department } = sessionUser;
return `${name} (${department})`;
}, [sessionUser]);
마무리
React에서 메모이제이션 방법을 통해서 불필요한 재랜더링을 막아서 성능을 개선할 수 있는 방법을 알아보았다.
- memo 함수를 통한 컴포넌트 메모이제이션
- useCallback 함수를 통한 함수 메모이제이션
- useMemo 함수를 통한 변수 메모이제이션
그렇다면 어디까지 메모이제이션을 통한 최적화를 진행해야 할까?
- 작은 컴포넌트나 시스템이 작은 경우에는 고려하지 않고, 성능이 문제가 되는 시점에 개선한다.
- Atomic Design 관점에서 atom(원자), molecules(분자) 해당하는 공통 컴포넌트의 경우 미리 고민한다.
- 컴포넌트에 데이터가 많을 것으로 예상되는 복잡한 컴포넌트 경우, 미리 고민하여 최적화 진행한다.