React Hook 2
1. useMemo
useMemo를 사용하면 함수형 컴포넌트 내부에서 발생하는 불필요한 연산을 최적화할 수 있다. 예를 들어 아래 코드에서는 input 값이 변경될 때마다 getAverage 함수를 호출한다.
Average.js
import React, { useState } from 'react';
const getAverage = numbers => {
console.log('평균값 계산');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
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('');
};
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>Average: </b> {getAverage(list)}
</div>
</div>
);
};
export default Average;
input 값이 변경될 때는 getAverage 함수를 호출할 필요가 없고 list 값이 변경되었을 때만 호출하면 되므로 이 때 useMemo를 사용하여 작업을 최적화할 수 있다. useMemo는 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용한다. 아래 코드는 useMemo를 사용하여 getAverage 함수 호출을 최적화한 코드이다.
Average.js
import React, { useState, useMemo } from 'react';
const getAverage = numbers => {
console.log('평균값 계산');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
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('');
};
// useMemo를 사용하여 list 값이 바뀔 때만 getAverage 함수 호출
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>Average: </b> {avg}
</div>
</div>
);
};
export default Average;
2. useCallback
useCallback은 useMemo와 비슷한 함수이다. 주로 렌더링 성능을 최적화해야 하는 상황에서 사용하는데 이 Hook을 사용하면 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다. 위에서 작성한 Average 컴포넌트의 onChage와 onInsert라는 함수는 컴포넌트가 리렌더링될 때마다 새로 생성된다.
컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트의 개수가 많아지면 이 부분을 최적화 해주는 것이 좋다. 아래는 useCallback을 사용하여 onChange와 onInsert를 최적화한 코드이다.
import React, { useState, useMemo, useCallback } from 'react';
const getAverage = numbers => {
console.log('평균값 계산');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
// 컴포넌트가 처음 렌더링될 때만 함수 생성
// 단순 설정이므로 빈 배열을 넣어도 상관없다.
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []);
// number 혹은 list가 변경되었을 때만 함수 생성
// 기존의 number와 list를 조회해서 nextList를 생성하기 때문에
// 배열 안에 number와 list를 꼭 넣어준다.
const onInsert = useCallback(
e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
},
[number, list]
);
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>Average: </b> {avg}
</div>
</div>
);
};
export default Average;
useCallback의 첫 번째 파라미터에는 생성하고 싶은 함수, 두 번째 파라미터에는 배열을 넣으면 된다. 이 배열에는 어떤 값이 변경되었을 때 함수를 새로 생성해야 하는지를 명시해준다. 빈 배열을 넣었을 경우엔 컴포넌트가 렌더링될 때 단 한번만 함수를 생성한다.
숫자, 문자열, 객체처럼 일반 값을 재사용하려면 useMemo를 사용하고, 함수를 재사용하려면 useCallback을 사용한다.
3. useRef
useRef는 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해준다. 아래 코드는 등록 버튼을 눌렀을 때 포커스가 input으로 가게 해준다.
import React, { useState, useMemo, useCallback, useRef } from 'react';
const getAverage = numbers => {
console.log('평균값 계산');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const inputEl = useRef(null);
// 컴포넌트가 처음 렌더링될 때만 함수 생성
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []);
// number 혹은 list가 변경되었을 때만 함수 생성
const onInsert = useCallback(
e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
// useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.
inputEl.current.focus(); // input box에 focus
},
[number, list]
);
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>Average: </b> {avg}
</div>
</div>
);
};
export default Average;
4. custom Hook
여러 컴포넌트에서 비슷한 기능을 공유할 경우, 이를 Hook으로 만들어 로직을 재사용할 수 있다. 아래 코드는 여러 개의 input을 관리하기 위한 useInputs라는 custom Hook이다.
useInputs.js
import { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
export default function useInputs(initialForm) {
const [state, dispatch] = useReducer(reducer, initialForm);
const onChange = e => {
dispatch(e.target);
};
return [state, onChange];
}
Info.js
import React from 'react';
import useInputs from './useInputs';
const Info = () => {
const [state, onChange] = useInputs({
name: '',
email: ''
});
const { name, email } = state;
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="email" value={email} onChange={onChange} />
</div>
<div>
<p>
<b>name: </b> {name}
</p>
<p>
<b>email: </b> {email}
</p>
</div>
</div>
);
};
5. etc
다른 개발자가 만든 Hook도 라이브러리로 설치하여 사용할 수 있다.
- Collection of React Hooks
https://nikgraf.github.io/react-hooks/ - awesome-react-hooks
https://github.com/rehooks/awesome-react-hooks
reference:
리액트 공식 문서
- Hooks API Reference
https://ko.reactjs.org/docs/hooks-reference.html