최적화
1. 최적화
- 최적화(Optimization)
- 웹 서비스의 성능을 개선하는 모든 행위
- 아주 단순한 것부터 어려운 방법까지 다양힘
- 일반적인 웹 서비스 최적화 방법
- 서버 응답속도 개선
- 이미지, 폰트, 코드파일 등 정적 파일 로딩 개선
- 불필요한 네트워크 요청 축소
- React App 내부의 최적화 방법
- 컴포넌트 내부의 불필요한 연산 방지
- 컴포넌트 내부의 불필요한 함수 재생성 방지
- 컴포넌트의 불필요한 리렌더링 방지
2. useMemo - 불필요한 연산 방지
- useMemo란?
- 메모이제이션 기법을 기반으로 불필요한 연산을 최적화하는 훅. (자매품: useCallback)
- Memoization: 컴퓨터 프로그램이 동일한 계산을 반복적으로 할때, 이전에 계산한 값을 메모리에 저장하여 중복적인 계산을 제거하여 전체적인 실행속도를 빠르게 하는 기법.
- 특정 조건이 만족했을때만 실행시킬 수 있다
- 메모이제이션 기법을 기반으로 불필요한 연산을 최적화하는 훅. (자매품: useCallback)
- useMemo 실습
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
// List.jsc
import './css/List.css';
import TodoItem from "./TodoItem";
import { useState } from "react";
const List = ({ todos, onUpdate, onDelete }) => {
const [search, setSearch] = useState("");
const onChangeSearch = (e) => {
setSearch(e.target.value);
}
const getFilteredData = () => {
if (search === "") {
return todos;
}
return todos.filter((todo) => todo.content.toLowerCase().includes(search.toLowerCase()));
}
const filteredData = getFilteredData();
// 현재 상태를 분석하여 수치로 제공하는 함수
const getAnalyzedData = () => {
const totalCount = todos.length;
const doneCount = todos.filter(todo => todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount
, doneCount
, notDoneCount
}
}
const {totalCount, doneCount, notDoneCount} = getAnalyzedData();
return (
<div className='List'>
<h4>Todo List 🌱</h4>
<div>
<div>total: {totalCount}</div>
<div>done: {doneCount}</div>
<div>notDone: {notDoneCount}</div>
</div>
<input value={search} onChange={onChangeSearch} placeholder='검색어를 입력하세요' />
<div className='todos_wrapper'>
{filteredData.map((todo) => {
return <TodoItem key={todo.id} {...todo} onUpdate={onUpdate} onDelete={onDelete} />;
})}
</div>
</div>
);
};
export default List;
- 먼저 현재 투두리스트의 수치를 계산하는 getAnalyzedData 함수를 만들었다
- 해당 함수는 리스트가 길어질수록 filter 메서드가 state의 배열 내의 모든 요소를 순회하기때문에 많은 부하가 생길것이다
- 현재는 해당 함수는 리스트 컴포넌트 내부에 있기때문에 리스트 컴포넌트가 리렌더링될때마다 계속 호출된다.
- 해당 함수가 불필요하게 호출되는걸 방지해야한다
1
useMemo(() => {}, [])
- 첫번째 인수 : 콜백함수
- 두번째 인수 : 의존성 배열
- useEffect와 같이 의존성배열에 들어간 변수의 값이 변경되면 콜백함수가 실행된다.
- 추가로, useEffect와 달리 useMemo는 콜백함수가 반환한 값을 그대로 다시 반환한다.
- 결과값을 받아 사용이 가능하다
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
import './css/List.css';
import TodoItem from "./TodoItem";
import { useState, useMemo } from "react";
const List = ({ todos, onUpdate, onDelete }) => {
const [search, setSearch] = useState("");
const onChangeSearch = (e) => {
setSearch(e.target.value);
}
const getFilteredData = () => {
if (search === "") {
return todos;
}
return todos.filter((todo) => todo.content.toLowerCase().includes(search.toLowerCase()));
}
const filteredData = getFilteredData();
const {totalCount, doneCount, notDoneCount} = useMemo(() => {
console.log("getAnalyzedData 호출");
const totalCount = todos.length;
const doneCount = todos.filter(todo => todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount
, doneCount
, notDoneCount
}
}, [todos]);
return (
<div className='List'>
<h4>Todo List 🌱</h4>
<div>
<div>total: {totalCount}</div>
<div>done: {doneCount}</div>
<div>notDone: {notDoneCount}</div>
</div>
<input value={search} onChange={onChangeSearch} placeholder='검색어를 입력하세요' />
<div className='todos_wrapper'>
{filteredData.map((todo) => {
return <TodoItem key={todo.id} {...todo} onUpdate={onUpdate} onDelete={onDelete} />;
})}
</div>
</div>
);
};
export default List;
3. Reat.memo - 불필요한 리렌더링 방지
- React.memo
- const MemoizedComponent = memo(Component)
- 리액트의 내장메서드
- 인수로 리액트의 컴포넌트를 받아 해당 컴포넌트의 최적화 기능을 추가하여 결과값으로 반환한다.
- 반환된 컴포넌트는 Props를 기준으로 메모이제이션된다.
- 즉, 반환된 컴포넌트는 부모컴포넌트가 리렌더링되더라도 자신이 받는 props가 바뀌지 않으면 리렌더링이 발생하지 않도록 최적화가 이루어지는것
- 불필요하게 리렌더링되는 컴포넌트 확인하기
- 개발자도구 -> 컴포넌트 -> 톱니바퀴 -> Highlight updates when components render 옵션 체크
- 예외경우
- Props로 객체타입을 받을 경우, memo 메서드는 얕은 비교를 하기때문에 주소값을 비교하게 됨.
- 부모 컴포넌트가 리렌더링될때마다 내부 함수 및 객체 변수들도 모두 같은것처럼 보이지만 새로 생성되고 있기 때문에 주소값이 변경되어 memo(Component) 만으론 의도대로 동작하지 않음
- 이를 방지하기 위해서는 두가지 방법이 있음
- 내부 함수 및 객체타입이 재생성되지 않도록 방지 - useCallback
- memo의 두번째인수로 콜백함수를 넣어 커스텀 진행
- 컴포넌트에 메모이제이션 등의 기능이 추가된 새로운 컴포넌트를 반환해주는 메서드를 리액트에서는 고차 함수 컴포넌트 (Higher Order Component. HOC) 라고 부름.
4. useCallback - 불필요한 함수 재생성 방지
- useCallback
1
const func = useCallback(fn, deps)
- fn: 반환함수
- deps: 의존성배열
- deps 가 변화하면 새로운 fn을 반환한다
5. 최적화?
- 최적화 타이밍
- 최적화가 너무 이르거나 너무 많아도 문제가 발생할 수 있음
- 이미 최적화가 되어있는 기능을 수정하게되면 최적화가 풀리게되거나 고장나게 되어버리는 경우가 생김
- 기능구현이 끝나고 프로젝트 마지막에 최적화 하는걸 권장함
- 최적화가 너무 이르거나 너무 많아도 문제가 발생할 수 있음
- 최적화 대상
- 모든것을에 최적화하는것은 안됨.
- 꼭 최적화가 필요할 것 같은 함수, 컴포넌트에만 최적화를 적용하는 것이 좋음
- 예시
- React.memo에도 연산이 필요함. props 비교, 메모리에 컴포넌트 값 보관 등,..
- 근데 헤더 컴포넌트와 같은 사소한 컴포넌트에 사용하게 되면 차라리 리렌더링이 더 빠를 수 있다.
- TodoItem 컴포넌트와 같이 유저의 행동에 따라 개수가 많아질 수 있거나, 함수를 많이 갖고있어 코드가 무거운 컴포넌트에 한해서 최적화를 수행하는 것을 권장함