리액트에 훅이 추가( 리액트 16.8버전 )되기 전에는 클래스 컴포넌트에서만 상태를 가질 수 있었음.
프로젝트가 커져가면서 상태를 스토어에 연결하거나 비슷한 로직을 가진 상태 업데이트 및 사이드 이펙트 처리가 불편해짐. 또한 모든 상태를 하나의 함수 내에서 처리하다 보니 관심사가 뒤섞이게 되었고 상태에 따른 테스트나 잘못 발생할 사이드 이펙트의 디버깅이 어려워짐.
리액트 훅이 도입되면서 함수 컴포넌트에서도 클래스 컴포넌트와 같이 컴포넌트의 생명주기에 맞처 로직을 실행할 수 있게 됨. 비즈니스 로직을 재사용하거나 작은 단위로 코드를 분할하여 테스트하는 게 용이해짐 & 사이드 이펙트와 상태를 관심사에 맞ㅈ게 분리하여 구성할 수 있게 됨.
.
.
.
: 리액트 함수 컴포넌트에서 상태를 관리
function useState<S>(
initialState: S | (() => S)
): [S, Dispatch<SetStateAction<S>>];
type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);
: useState가 반환하는 튜플
튜블 첫번째 요소 : 제네릭으로 지정한 S 타입
튜블 두번째 요소 : 상태를 업데이트할 . 수있는 Dispatch 타입의 함수
의존성 배열은 이 훅들이 언제 실행될지를 결정하는 중요한 역할을 함.
useEffect
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
type DependencyList = ReadonlyArray<any>;
type EffectCallback = () => void | Destructor;
React에서 비동기적 사이드 이펙트를 처리하기 위해 사용.
: 첫 번째 인자 (effect 타입)인 EffectCallback은 Destructor를 반환하거나 아무것도 반환하지 않는 함수. Promise 타입은 반환하지 않으므로 useEffect의 콜백 함수에는 비동기 함수가 들어갈 수 없다. useEffect에서 비동기 함수를 호출할 수 있다면 경쟁 상태를 불러일으킬 수 있기 때문.
경쟁 상태 Race Condition
: 멀티스레딩 환경에서 동시에서 여러 프로세스 스레드가 공유된 자원에 접근하려고 할 때 발생할 수 있는 문제. 이러한 상황에서 실행 순서나 타이밍을 예측할 수 없게 되어 프로그램 동작이 원하지 않는 방향으로 흐를 수 있다.
렌더링 이후 리액트 함수 컴포넌트에 어떤 일을 수행해야 하는지 알려줌.
컴포넌트가 렌더링된 후 DOM 업데이트가 완료된 시점에 실행.
주로 API 호출, 데이터 페칭, 구독 설정, 타이머 등 비차단 작업에 적합.
브라우저가 페인팅을 완료한 후에 실행되므로 사용자 경험에 영향을 덜 줌.
useLayoutEffect
-코드-
useEffect
보다 더 즉각적인 작업에 유용.const [name, setName] = useState("");
useEffect (() => {
// 매우 긴 시간이 흐른 뒤 아래의 SetName을 실행한다고 생각하자
setName("배달이");
},[]);
return (
<div>
{`안녕하세요, ${name}님!`}
</div>
);
처음에는 "안녕하세요, 님!" : name이 빈칸으로 렌더링
다시 "안녕하세요, 배달이님!"으로 변경되어 렌더링될 것.
만약 name을 지정하는 setName이 오랜 시간이 걸린 후에 실행된다면 사용자는 빈 이름을 오랫동안 보고 있어야 함.
useLayoutEffect
사용 -> 화면에 해당 컴포넌트가 그려지기 전에 콜백 함수 실행, 첫 번째 렌더링 때 빈 이름이 뜨는 경우 방지 가능.
useEffect
: 비동기 작업 및 비차단적인 작업에 사용.useLayoutEffect
: 동기적 작업 및 DOM 레이아웃 조작에 사용.: 이전에 생성된 값 또는 함수를 기억하며, 동일한 값과 함수를 반복해서 생성하지 않도록 해주는 훅.
: 어떤 값을 계산하는 데 오랜 시간이 걸릴 때나 렌더링이 자주 발생하는 form에서 useMemo나 useCallback을 유용하게 사용 가능.
: 과도하게 메모제이션 -> 컴포넌트의 성능 향상이 보장되지 않을 수 있음.
메모제이션 Memoization
: 이번에 계산한 값을 저장함으로써 같은 입력에 대한 연산을 다시 수행하지 않도록 최적화하는 기술.
useMemo
const computedValue = useMemo(() => expensiveCalculation(data), [data]);
useCallback
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []);
간단 비교:
useMemo
: 값(연산 결과)을 캐싱하여 계산을 최적화.useCallback
: 함수를 캐싱하여 재생성 방지 및 메모리 낭비 감소.: DOM을 직접 선택해야 하는 경우(<input />
요소에 포커스 설정하거나 특정 컴포넌트의 위치로 스크롤 하는 등)
-> 리액트의 useRef를 사용.
useRef
는 부모 컴포넌트에서 자식 컴포넌트의 DOM 엘리먼트나 특정 값을 참조하는 데 사용됨.ref
를 전달하면 부모에서 자식의 DOM 엘리먼트를 직접 접근할 수 있음.useImperativeHandle
은 부모 컴포넌트가 전달받은 ref
를 통해 자식 컴포넌트의 특정 메서드나 속성을 명시적으로 노출할 수 있도록 함.useRef
와 함께 사용되며, 자식 컴포넌트 내부의 복잡한 동작을 외부에서 간단히 트리거할 수 있게 설계됨.useRef
는 값이 변경되어도 리렌더링을 발생하지 않음. ( 상태가 변경되더라도 불필요한 리렌디렁을 피할 수 있음. )훅의 규칙
: 리액트 훅을 안전하게 사용하기 위해 지켜야할 2가지 규칙
1. 훅은 항상 최상위 레벨에서 노출
: 이렇게 해야 useState나 useEffect가 여러 번 호출되더라도 훅의 상태를 올바르게 유지 가능.
2. 훅은 항상 함수 컴포넌트나 커스텀 훅 등의 리액트 컴포넌트 내에서만 호출되어야 함.
-> 규칙 필요 이유 : 리액트에서 훅은 호출 순서에 의존하기 때문.
.
.
.
사용자 정의 훅 생성 -> 컴포넌트 로직을 함수로 뽑아내 재사용 가능
커스텀 훅은 리액트 컴포넌트 내에서만 사용 가능.
이름은 반드시 use로 시작.
useInput(예시로 만든 커스텀 훅)은 인자로 받은 초깃값을 useState로 관리,
해당 값을 수정할 수 있는 onChange 함수를 input의 값과 함께 반환하는 훅
useInput 함수의 인자로 넣어준 initialValue, onChange 함수의 인자로 넣어준 e의 타입이 지정되지 않았기 때문에 발생하는 에러로 두 군데 모두 타입을 명시적으로 정의해주면 해결됨.
IDE를 활용하면 타입스크립트 컴파일러(tsc)가 현재 사용하고 있는 이벤트 객체의 타입을 유추해서 알려주므로 유용함.