1. hooks ⭐
1.1 useMemo
- useMemo를 사용하면
함수형 컴포넌트
내부에서 발생하는 연산을 최적화할 수 있다.
src/Average.js
파일을 생성하여 리스트에 숫자를 추가하면 추가된 숫자들의 평균을 보여주는 함수형 컴포넌트
의 코드를 작성했다.
//Average.js
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>평균값: </b> {getAverage(list)}
</div>
</div>
);
};
- 하지만 위의 코드는 Input 내용이 수정될 때도
getAverage
함수가 호출된다.
* 즉, 낭비
를 하고 있는 것임.❗
- 이를 해결하기 위해 useMemo를 사용하여 특정 값이 바뀌었을 때만 연산을 실행하도록 다음과 같이 코드를 수정했다.
//MemoAverage.js
const getAverage = numbers => {
console.log('평균값 계산 중');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b)
return sum / numbers.length;
};
const MemoAverage = () => {
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 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>평균값: </b> {avg}
</div>
</div>
);
};
- 위의 코드를 사용하면 list 배열의 내용이 바뀔 때만
getAverage
함수가 호출된다.
1.2 useCallback
- useCallback은 앞서 살펴본 useMemo와 비슷한 함수이다.
* 주로 렌더링 성능을 최적화해야 하는 상황에 사용된다.
- useCallback
hook
을 사용하면 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다.
1.1 절
에서 구현한 컴포넌트를 보면 onChange
와 onInsert
라는 함수를 선언했는데, 이는 컴포넌트가 리렌더링될 때마다 함수들이 새로 생성된다.❗
- 즉, 컴포넌트의 렌더링이 자주 발생하거나, 렌더링해야 할 컴포넌트의 개수가 많을 경우
useCallback
을 사용하여 최적화할 수 있다.
//CallbackAverage.js
const onChange = useCallback(e=> {
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}, [number, list]); // number || list가 바뀌었을 때만 함수 생성
- useCallback의 첫 번째 파라미터에는 생성하고 싶은 함수를 넣고, 두 번째 파라미터에는 배열을 넣어 사용한다.
* 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시해야함.❗❗
TIP ✍
- 함수 내부에서 상태 값에 의존해야 할 경우 반드시 그 값을 두 번째 파라미터 안에 포함해야한다❗❗
숫자
, 문자열
, 객체
처럼 일반 값을 재사용 하려면 useMemo
를 사용하고, 함수를 재사용 하려면 useCallback
을 사용한다고 이해하자.
1.3 useRef
- useRef Hook은 함수형 컴포넌트에서
ref
를 쉽게 사용할 수 있도록 해준다.
1.2 절
코드를 활용하여 저번에 해봤던 포커스가 인풋으로 넘어가는 코드는 아래와 같다.
//CallbackAverage.js
const getAverage = numbers => {
console.log('평균값 계산 중');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b)
return sum / numbers.length;
};
const CallbackAverage = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const inputEl = useRef(null)
const onChange = useCallback(e=> {
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
inputEl.current.focus();
}, [number, list]); // 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>평균값: </b> {avg}
</div>
</div>
);
};
- useRef를 사용하여
ref
를 설정하면 useRef를 통해 만든 객체 안의 current
값이 실제 엘리먼트
를 가르킨다.
TIP ❗❗
- 추가적으로 컴포넌트 로컬 변수를 사용해야 할 때도
useRef
를 활용할 수 있다.
* 로컬 변수란 렌더링
과 상관없이 바뀔 수 있는 값이라고 이해하자.❗
- 하지만,
렌더링
과 관련되지 않은 값을 관리할 때만 사용해야 한다.
1.4 커스텀 Hooks 만들기
- 여러 컴포넌트에서 비슷한 기능을 공유할 경우,
Hooks
으로 로직을 재사용할 수 있다.
- 예를들어, [React] 10. Hooks(1)에서 사용했던
InputInfo.js
컴포넌트에서 여러 개의 인풋을 관리하기 위해 작성했던 로직을 CustomInputs
라는 Hook
으로 따로 분리한 코드는 아래와 같다.
//CustomInputs.js
import { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
export default function CustomInputs(initialForm) {
const [state, dispatch] = useReducer(reducer, initialForm);
const onChange = e => {
dispatch(e.target);
};
return [state, onChange];
}
CustomInputs
Hook을 사용하기 위해 src/InputInfo.js
를 수정하여 src/CustomInfo.js
파일에 코드를 작성했다.
//CustomInfo.js
const CustomInfo = () => {
const [state, onChange] = CustomInputs({
name: '',
nickname: ''
})
const { name, nickname } = state;
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름: </b> {name}
</div>
<div>
<b>닉네임: </b> {nickname}
</div>
</div>
</div>
);
};
- 위와 같이 커스텀 Hooks를 만들어서 사용한 것처럼, 아래의 사이트에서 다른 개발자가 만든 Hooks도
라이브러리
로 설치하여 사용할 수 있다.
- react-hooks
- awesome-react-hooks
클래스형 컴포넌트로 프로젝트를 진행하는 것은 문제가 아니지만 그래도 되도록이면 함수형 컴포넌트와 Hooks를 사용하는 형태로 컴포넌트를 구현하자..❗ 그리고 계속 했던 실수로 렌더링
, 함수 호출
을 해야할 때 경로를 제대로 설정하지 못했다..
절대경로, 파일명(대소문자) 구분을 잘하자..😂end