React는 변화를 감지할 경우 컴포넌트를 다시 렌더링한다.
그런데 이 과정에서 불필요하게 연산을 다시 하는 일도 많이 나타나며,
이런 것들이 누적되면 성능의 저하로 이어진다.
이런 불필요한 연산을 막아 성능을 개선시켜주기 위해 사용하는 훅이 useMemo, useCallback이다.
1. useMemo
useMemo는 특정 값이 바뀌었을 때만 연산을 수행하고, 그 값이 바뀌지 않았다면 이전 연산 결과를 재사용한다.
다음과 같이 입력한 수들의 평균값을 계산하는 툴이 있다고 하자.
/* src/useMemo/UseMemo.jsx */
import React, {useState} from 'react'
const Average = () => {
const [list, setList] = useState('')
const [number, setNumber] = useState('')
const onChange = (e) => {
setNumber(e.target.value)
return(
<>
<input type='text' value={number} onChange={onChange}/>
<button> add Number </button>
<ul>{list.map((v,k) => (<li key={k}>{v}</li>))}</ul>
<div><b>Average:</b> </div>
</>
}
export default Average
input에 숫자를 넣고 add Number 버튼을 클릭하면 배열 list에 해당 숫자를 추가하고 지금까지 넣어준 숫자들의 목록에
렌더링 후, Average값을 계산한다.
list 배열에 값을 추가하는 함수를 버튼 element에 추가한다.
/* src/useMemo/UseMemo.jsx */
const Average = () => {
...중략
const onInsert = () => {
const newList = list.concat(parseInt(number))
setlist(newList)
setNumber('')
}
return(
<>
<input type='text' value={number} onChange={onChnage} />
<button onClick={onInsert}>register</button>
<ul>
{list.map((v,k) => (<li key={k}>{v}</li>))}
</ul>
<div> Average : </div>
</>
)
마지막으로 평균값을 계산해 출력하는 함수를 추가한다.
/* src/useMemo/UseMemo.jsx */
import React, { useState } from 'react'
const Average = () => {
const [list, setList] = useState([])
const [number, setNumber] = useState('')
const onChange = e => {
setNumber(e.target.value)
}
const onInsert = e => {
const nextList = list.concat(parseInt(number))
setList(nextList)
setNumber('')
}
const getAverage = (numbers) => {
console.log('calculate...')
if(numbers.length === 0) {return 0}
const sum = numbers.reduce((a, b) => a+b )
return sum/numbers.length
}
return(
<>
<input type='text' value={number} onChange={onChange}/>
<button onClick={onInsert}>register</button>
<ul>
{list.map((v,k) => (<li key={k}>{v}</li>))}
</ul>
<div><b>Average:</b>{getAverage(list)}</div>
</>
)
}
export default Average
문제는 이러면 우리가 타이핑을 할 때마다 (숫자를 입력할 때마다) 리렌더링되는데,
(calculate... 가 타이핑에 따라 계속 콘솔에 찍히는걸 확인할 수 있다.)
이 과정에서 getAverage도 계속해서 다시 실행되는 불필요한 연산이 일어난다는 것에 있다.
getAverage 함수는 우리가 register 버튼을 클릭할때만 실행이 되어 값을 최신화해주면 된다.
이를 구현하기 위해 사용하는 것이 useMemo이다.
useMemo는 다음과 같은 형식으로 사용하면 된다.
const [변수명] = useMemo(실행을 컨트롤할 함수, 그 함수가 변화를 인식할 변수)
/* src/useMemo/UseMemo.jsx */
import React, { useState, useMemo } from 'react'
const Average = () => {
...중략
const avg = useMemo(() => getAverage(list), [list])
return(
<>
...중략
<div><b> Average: </b> {avg} </div>
</>
)
}
실행해 조건을 붙이고 싶은 함수를 useMemo 안에 감싸고 이를 변수에 담는다. 두 번째 인자에는 getAverage가 변화를
인식할 변수 list를 담는다.
다시 말해, useMemo의 첫 번째 인자 getAverage함수는 두 번째 인자 list에 변화가 생겼을 때만 이를 인식해 실행된다.
이를 담은 변수 avg를 getAverage대신 return 안에 써주면
이제 버튼을 클릭해 onInsert함수가 실행되고,
그에 따라 list의 값이 바뀌는 것을 인식해 그 때만 getAverage함수가 실행되어 값이 바뀌게 된다.
2. useCallback
여기서 한 단계 더 성능을 개선하기 위해 useCallback 훅을 활용해보자.
useCallback도 useMemo와 비슷한 함수로, 만들어둔 함수를 재사용한다.
컴포넌트가 리렌더링될 때, 다시 그 안의 함수를 선언하지 않고 리렌더링 되기 전 함수를 그대로 사용한다는 것이다.
사용 형식도 useMemo와 비슷하다.
원하는 함수를 useCallback의 첫 번째 인자값으로 주고, 참조할 변수(들)을 두 번째 인자값으로 준다.
const 변수명 = useCallback( () => { 함수 }, [ 참조 변수값 ])
Average 컴포넌트 안의 함수 onChange, onInsert 함수를 바꿔보자.
/* src/useMemo/Usememo.jsx */
import React, { useState, usememo, useCallback } from 'react'
// 구조 분해 할당에 useCallback을 추가
const Average = () => {
...중략
const onChange = useCallback((e) => {
setNumber(e.target.value)
}, []
)
const onInsert = useCallback(() => {
const newList = list.concat(parseInt(number))
setList(newList)
setNumber('')
}, [number, list]
)
}
...중략
}
onChange 함수는 그냥 number값을 우리가 입력한 숫자로 바꿔주는 함수이니 다른 변수값을 참조하거나
이를 매개변수로 사용해 연산할 필요가 없으므로 두 번째 인자값은 비워준다.
반면 onInsert 함수는 기존의 number, list 값을 기반으로 newList를 생성해야하므로 두 번째 인자값에
number, list가 필요하다.
'React' 카테고리의 다른 글
#16 Redux part2 (0) | 2022.05.07 |
---|---|
#15 Redux part1 (0) | 2022.05.07 |
#13 useReducer (0) | 2022.05.01 |
#12 useContext (0) | 2022.04.29 |
#11 함수형 컴포넌트 2. useEffect (0) | 2022.04.29 |