Hook은 함수 컴포넌트에서 사용하는 메서드입니다.
function Counter () {
const [counter, setCounter] = useState(0);
const handleIncrease = () => {
setCounter(counter + 1)
}
return (
<div>
<p>You clicked {counter} times</p>
<button onClick={handleIncrease}>
Click me
</button>
</div>
)
}
Counter
컴포넌트에서 useState()
Hook을 호출해 함수 컴포넌트(function component)안에 state를 추가한 형태입니다. 이 state는 컴포넌트가 리렌더링 되어도 그대로 유지됩니다.
리액트 함수의 최상위에서만 호출해야 합니다.
반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 예상한 대로 동작하지 않을 우려가 있습니다.
React는 이 Hook을 호출되는 순서대로 저장해놓기 때문에 조건문, 반복문 안에서 예기치 못한 버그가 발생할 수 있습니다.
오직 리액트 함수 내에서만 사용되어야 합니다.
이는 리액트 함수형 컴포넌트나 커스텀 Hook이 아닌 다른 일반 JavaScript 함수 안에서 호출해서는 안 된다는 의미입니다.
애초에 Hook은 React의 함수 컴포넌트 내에서 사용되도록 만들어진 메소드이기 때문에 근본적으로 일반 JavaScript 함수 내에서는 정상적으로 돌아가지 않습니다.
React Hook은 함수 컴포넌트가 상태를 조작하고 최적화 기능을 사용할 수 있게끔하는 메소드입니다. 그 중 렌더링 최적화를 위한 Hook도 존재하는데, useCallback과 useMemo가 바로 그 역할을 하는 Hook입니다.
useMemo는 특정 값(value)를 재사용하고자 할 때 사용하는 Hook입니다.
function Calculator({value}){
const result = calculate(value);
return <>
<div>
{result}
</div>
</>;
}
calculate
가 내부적으로 복잡한 연산을 해야하는 함수라고 가정한다면 해당 컴포넌트는 렌더링할 때마다 이 함수를 계속해서 호출할 것이고, 시간이 몇 초 이상 소요될 것입니다.
/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import { useMemo } from "react";
function Calculator({value}){
const result = useMemo(() => calculate(value), [value]);
return <>
<div>
{result}
</div>
</>;
}
이런 식으로 useMemo
를 호출하여 calculate
함수를 감싸주면, 이전에 구축된 렌더링과 새로이 구축되는 렌더링을 비교해 value
값이 동일할 경우에는 굳이 calculate
함수를 호출할 필요도 없이 이전 렌더링의 result
값을 그대로 재활용할 수 있게 됩니다. 이는 메모이제이션(Memoization)개념과 긴밀한 관계가 있습니다.
메모이제이션(Memoization)은 기존에 수행한 연산의 결과값을 메모리에 저장해두고, 동일한 입력이 들어오면 재활용하는 기법을 말합니다. 굳이 중복 연산을 할 필요가 없기 때문에 앱의 성능을 최적화할 수 있습니다.
useCallback 또한 useMemo와 마찬가지로 메모이제이션 기법을 이용한 Hook입니다. useMemo는 값의 재사용을 위해 사용하는 Hook이라면, useCallback은 함수의 재사용을 위해 사용하는 Hook입니다.
function Calculator({x, y}){
const add = () => x + y;
return <>
<div>
{add()}
</div>
</>;
}
add
함수는 props로 넘어온 x
와 y
값을 더해<div>
태그에 값을 출력합니다.
이 함수는 해당 컴포넌트가 렌더링 될 때마다 새롭게 만들어집니다.
useCallback
은 useMemo
처럼 함수를 호출하지 않는 Hook이 아니라
메모리 어딘가에 함수를 꺼내서 호출하는 Hook입니다.
따라서, 단순히 컴포넌트 내에서 함수를 반복해서 생성하지 않기 위해 useCallback
을 사용하는 것은 큰 의미가 없거나 오히려 손해인 경우도 있습니다.
useCallback
을 사용하기 좋은 경우는 자식 컴포넌트의 props로 함수를 전달해줄 때입니다.
/* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import React, { useCallback } from "react";
function Calculator({x, y}){
const add = useCallback(() => x + y, [x, y]);
return <>
<div>
{add()}
</div>
</>;
}
useCallback
은 참조 동등성에 의존합니다. React는 JavaScript 언어로 만들어진 오픈소스 라이브러리이기 때문에 기본적으로 JavaScript의 문법을 따라갑니다.
JavaScript에서 함수는 객체입니다. 객체는 메모리에 저장할 때 값을 저장하는 게 아니라 값의 주소를 저장하기 때문에 반환하는 값이 같을 지라도 일치연산자로 비교했을 때 false
가 출력됩니다.
function doubleFactory(){
return (a) => 2 * a;
}
const double1 = doubleFactory();
const double2 = doubleFactory();
double1(8); // 16
double2(8); // 16
double1 === double2; // false
double1 === double1; // true
이는 React 또한 같습니다. React는 리렌더링 시 함수를 새로이 만들어서 호출합니다. 새로이 만들어 호출된 함수는 기존의 함수와 같은 함수가 아닙니다. 따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 Prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있습니다.
useCallback
을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다고 볼 수 있습니다.
개뱔자가 스스로 커스텀한 훅을 의미하며 이를 이용한 반복되는 로직을 함수로 뽑아내어 재사용할 수 있습니다.
여러 url을 fetch할 때, 여러 input에 의한 상태 변경 등 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때 커스텀 훅을 주로 사용합니다.
장점으로는
1. 상태관리 로직의 재활용이 가능하다.
2. 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현할 수 있다.
3. 함수형으로 작성하기 때문에 보다 명료하다.
가 있습니다.
Custom Hook을 정의할 때는 일종의 규칙이 필요합니다.
use
를 붙이는 것이 규칙입니다.일반 함수 내부에서는 React 내장 Hook을 불러 사용할 수 없지만 Custom Hook에서는 가능합니다.
useFetch
Hookconst useFetch = ( initialUrl:string ) => {
const [url, setUrl] = useState(initialUrl);
const [value, setValue] = useState('');
const fetchData = () => axios.get(url).then(({data}) => setValue(data));
useEffect(() => {
fetchData();
},[url]);
return [value];
};
export default useFetch;
[코드] 여러 input에 의한 상태 변경을 할 때 쓸 수 있는 useInputs
Hooks
import { useState, useCallback } from 'react';
function useInputs(initialForm) {
const [form, setForm] = useState(initialForm);
// change
const onChange = useCallback(e => {
const { name, value } = e.target;
setForm(form => ({ ...form, [name]: value }));
}, []);
const reset = useCallback(() => setForm(initialForm), [initialForm]);
return [form, onChange, reset];
}
export default useInputs;