TypeScript_STUDY 9장 _ 훅 [ 9.1 리액트 훅 | 9.2 커스텀 훅 ]

zeroha·2024년 12월 23일
0

TypeScriptStudy

목록 보기
26/32
post-thumbnail

9.1 리액트 훅

리액트에 훅이 추가( 리액트 16.8버전 )되기 전에는 클래스 컴포넌트에서만 상태를 가질 수 있었음.

프로젝트가 커져가면서 상태를 스토어에 연결하거나 비슷한 로직을 가진 상태 업데이트 및 사이드 이펙트 처리가 불편해짐. 또한 모든 상태를 하나의 함수 내에서 처리하다 보니 관심사가 뒤섞이게 되었고 상태에 따른 테스트나 잘못 발생할 사이드 이펙트의 디버깅이 어려워짐.

리액트 훅이 도입되면서 함수 컴포넌트에서도 클래스 컴포넌트와 같이 컴포넌트의 생명주기에 맞처 로직을 실행할 수 있게 됨. 비즈니스 로직을 재사용하거나 작은 단위로 코드를 분할하여 테스트하는 게 용이해짐 & 사이드 이펙트와 상태를 관심사에 맞ㅈ게 분리하여 구성할 수 있게 됨.

.
.
.


1. useState

: 리액트 함수 컴포넌트에서 상태를 관리

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 타입의 함수


2. 의존성 배열을 사용하는 훅

의존성 배열은 이 훅들이 언제 실행될지를 결정하는 중요한 역할을 함.

1) useEffect와 useLayoutEffect

  • 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
    -코드-

    • DOM이 업데이트된 직후, 브라우저가 화면에 그리기 전에 동기적으로 실행.
    • DOM 읽기 및 쓰기가 필요한 작업(예: 레이아웃 측정, DOM 수정)에 적합.
    • 사용 중 DOM을 변경하면 레이아웃 재계산이 발생할 수 있어 성능에 영향을 줄 가능성이 있음.
    • 실행 타이밍이 더 빠르기 때문에 useEffect보다 더 즉각적인 작업에 유용.
const [name, setName] = useState("");

useEffect (() => {
// 매우 긴 시간이 흐른 뒤 아래의 SetName을 실행한다고 생각하자
  setName("배달이");,[]);

return (
  <div>
  	{`안녕하세요, ${name}님!`}
  </div>
  );

처음에는 "안녕하세요, 님!" : name이 빈칸으로 렌더링
다시 "안녕하세요, 배달이님!"으로 변경되어 렌더링될 것.
만약 name을 지정하는 setName이 오랜 시간이 걸린 후에 실행된다면 사용자는 빈 이름을 오랫동안 보고 있어야 함.
useLayoutEffect 사용 -> 화면에 해당 컴포넌트가 그려지기 전에 콜백 함수 실행, 첫 번째 렌더링 때 빈 이름이 뜨는 경우 방지 가능.

  • 간단 비교 :
    • useEffect: 비동기 작업 및 비차단적인 작업에 사용.
    • useLayoutEffect: 동기적 작업 및 DOM 레이아웃 조작에 사용.

2) useMemo와 useCallback

: 이전에 생성된 값 또는 함수를 기억하며, 동일한 값과 함수를 반복해서 생성하지 않도록 해주는 훅.
: 어떤 값을 계산하는 데 오랜 시간이 걸릴 때나 렌더링이 자주 발생하는 form에서 useMemo나 useCallback을 유용하게 사용 가능.

: 과도하게 메모제이션 -> 컴포넌트의 성능 향상이 보장되지 않을 수 있음.

메모제이션 Memoization
: 이번에 계산한 값을 저장함으로써 같은 입력에 대한 연산을 다시 수행하지 않도록 최적화하는 기술.

  • useMemo

    • 값의 연산 결과를 캐싱하기 위해 사용.
    • 컴포넌트가 렌더링될 때 특정 값이 변경되지 않으면 이전에 계산된 결과를 재사용.
    • 복잡한 연산이나 비용이 큰 계산이 포함된 작업을 최적화하는 데 적합.
    • 의존성 배열에 포함된 값이 변경될 때만 새 값을 계산.
    const computedValue = useMemo(() => expensiveCalculation(data), [data]);
  • useCallback

    • 함수 자체를 캐싱하기 위해 사용.
    • 컴포넌트가 재렌더링될 때 동일한 함수 객체를 재사용.
    • 자식 컴포넌트에 콜백 함수를 전달하거나, 이벤트 핸들러를 최적화할 때 적합.
    • 의존성 배열에 포함된 값이 변경될 때만 새로운 함수 생성.
    const handleClick = useCallback(() => {
      console.log("Button clicked");
    }, []);
  • 간단 비교:

    • useMemo: 값(연산 결과)을 캐싱하여 계산을 최적화.
    • useCallback: 함수를 캐싱하여 재생성 방지 및 메모리 낭비 감소.

3. useRef

: DOM을 직접 선택해야 하는 경우(<input /> 요소에 포커스 설정하거나 특정 컴포넌트의 위치로 스크롤 하는 등)
-> 리액트의 useRef를 사용.

1) 자식 컴포넌트에 ref 전달하기

  • useRef는 부모 컴포넌트에서 자식 컴포넌트의 DOM 엘리먼트나 특정 값을 참조하는 데 사용됨.
  • 자식 컴포넌트에 ref를 전달하면 부모에서 자식의 DOM 엘리먼트를 직접 접근할 수 있음.
  • 예를 들어, 스크롤 제어, DOM 조작 등이 필요할 때 활용됨.
  • 이를 통해 컴포넌트 간 더 세부적인 상호작용이 가능함.

2) useImperativeHandle

  • useImperativeHandle은 부모 컴포넌트가 전달받은 ref를 통해 자식 컴포넌트의 특정 메서드나 속성을 명시적으로 노출할 수 있도록 함.
  • useRef와 함께 사용되며, 자식 컴포넌트 내부의 복잡한 동작을 외부에서 간단히 트리거할 수 있게 설계됨.
  • 불필요한 상태 공유 없이도 컴포넌트의 동작을 캡슐화하고 외부에 공개 가능.
  • 자식 컴포넌트를 커스터마이징하거나 특정 동작을 제어할 때 유용함.

3) useRef의 여러 가지 특성

  • useRef는 값이 변경되어도 리렌더링을 발생하지 않음. ( 상태가 변경되더라도 불필요한 리렌디렁을 피할 수 있음. )
  • 컴포넌트 생애 주기 동안 참조값이 유지되며, 렌더링 간에 데이터를 공유하기 위해 주로 사용됨.
  • DOM 엘리먼트 참조뿐 아니라, 상태를 추적하거나 렌더링 간 데이터를 유지할 때도 활용 가능.
  • 특히, 타이머 ID, 이전 값 저장 등 비DOM 상태 관리에 유용함.

훅의 규칙
: 리액트 훅을 안전하게 사용하기 위해 지켜야할 2가지 규칙
1. 훅은 항상 최상위 레벨에서 노출
: 이렇게 해야 useState나 useEffect가 여러 번 호출되더라도 훅의 상태를 올바르게 유지 가능.
2. 훅은 항상 함수 컴포넌트나 커스텀 훅 등의 리액트 컴포넌트 내에서만 호출되어야 함.

-> 규칙 필요 이유 : 리액트에서 훅은 호출 순서에 의존하기 때문.

9.2 커스텀 훅

.
.
.

1. 나만의 훅 만들기

사용자 정의 훅 생성 -> 컴포넌트 로직을 함수로 뽑아내 재사용 가능

커스텀 훅은 리액트 컴포넌트 내에서만 사용 가능.
이름은 반드시 use로 시작.

useInput(예시로 만든 커스텀 훅)은 인자로 받은 초깃값을 useState로 관리,
해당 값을 수정할 수 있는 onChange 함수를 input의 값과 함께 반환하는 훅


2. 타입스크립트로 커스텀 훅 강화하기

useInput 함수의 인자로 넣어준 initialValue, onChange 함수의 인자로 넣어준 e의 타입이 지정되지 않았기 때문에 발생하는 에러로 두 군데 모두 타입을 명시적으로 정의해주면 해결됨.

IDE를 활용하면 타입스크립트 컴파일러(tsc)가 현재 사용하고 있는 이벤트 객체의 타입을 유추해서 알려주므로 유용함.


도서참조 : 우아한 타입스크립트 with 리액트
profile
하 영

0개의 댓글

관련 채용 정보