노트 목록

리액트 훅 깊게 살펴보기

리액트의 모든 훅 파헤치기

  • useState

    • 게으른 초기화 방식이 있다. useState의 초깃값이 복잡하거나 무거운 연산을 포함하고 있을 때 사용해야 한다.
    • 다음과 같은 형태로 사용한다.
      게으른 초기화 이후 리렌더링이 발생된다면 이 함수는 무시된다.
    const [state, setState] = useState(() => {
      const initialState = someExpensiveComputation(props);
      return initialState;
    });
    const [state, setState] = useState(() => {
      const initialState = someExpensiveComputation(props);
      return initialState;
    });
    • 일반적으로 사용하는 useState방식으로 사용하면 랜더링이 될 때 useState의 값도 재실행이 된다.
    • 클로저로 구현을 했기 때문에 초깃값은 최초에만 사용이 된다. 만약 useState에 인수로 자바스크립트에 많은 비용을 요구하는 작업이 있다면, 계속 실행될 위험이 존재한다.
    • 게으른 초기화 방식을 이용하면 최초 렌더링 이후에는 실행되지 않고, 최초의 state값을 넣을 때만 실행된다.
  • useEffect

    • 애플리케이션 내 컴포넌트의 여러 값들을 활용해 동기적으로 부수 효과를 만드는 훅이다.
    • useEffect에 의존성 배열을 넣지 않으면 매번 컴포넌트 랜더링 마다 실행이 되므로 useEffect없이 로직을 작성해도 될까?
      • 아니다. useEffect는 클라이언트 사이드에서 실행되는 것을 보장해준다.
      • useEffect는 컴포넌트 렌더링의 부수 효과이므로 컴포넌트의 렌더링이 완료된 이후 실행된다.
      • 직접 실행을 하게 될 경우 컴포넌트가 렌더링되는 도중에 실행된다.
    • 리액트는 값을 비교할 때 얕은 비교(1장에서 설명)를 한다. 따라서 useEffect의 의존성 배열도 얕은 비교로 변경사항을 감지한다.
    • useEffect 의존성 배열에 빈 배열을 넣을땐 신중하자
      • useEffect의 본질은 반드시 의존성 배열로 전달한 값의 변경에 의해 실행돼야 하는 것이다.
      • 빈 배열을 넘기는 것은 state,props와 같은 어떤 값의 변경과 별개로 useEffect의 부수효과가 발생한다는 의미이다.
      • 정말 의존성으로 빈 배열이 필요하다면 "정말 useEffect의 부수 효과가 컴포넌트의 상태와 별개로 작동해야만 하는지" 또는 "해당 컴포넌트에서 호출을 해야 하는지" 검토를 하자.
  • useMemo

    • useMemo(() => <ExpensiveComponent valuue={value} /> , [value]) 다음과 같이 컴포넌트 자체를 메모이제이션할 수도 있다.물론 React.memo를 사용하는게 더 현명하다.
  • useCallback

    • useMemo를 래핑한 함수이다. 메모이제이션을 하는 대상이 함수이다.
  • useRef

    • 랜더링에 영향을 미치지 않는 고정된 값을 관리하기 위해 useRef를 사용한다.
    • 그렇다면 아래와 같은 형태로 사용해도 되는거 아닐까?
    let value = 0;
     
    function Component() {
      function handleClick() {
        value += 1;
      }
     
      // ...
    }
    let value = 0;
     
    function Component() {
      function handleClick() {
        value += 1;
      }
     
      // ...
    }
    • 그렇지 않다. 컴포넌트가 여러 번 생성되어도 가르키는 값이 모두 value로 동일하다. 이는 모듈 캐싱과 연관이 있다.
    • useRef는 아래와 같이 구현이 되어있다. current의 값이 변경이 되어도 랜더링마다 동일한 객체를 가르키게 된다.
    export function useRef(initialValue) {
      currentHook = 5;
      return useMemo(() => ({ current: initialValue }), []);
    }
    export function useRef(initialValue) {
      currentHook = 5;
      return useMemo(() => ({ current: initialValue }), []);
    }
  • useContext

    • 상위 컴포넌트에서 만들어진 Context를 함수형 컴포넌트에서 사용할 수 있도록 만들어진 훅이다.
    • useContext를 사용하면 상위 컴포넌트 어딘가에 선언된 <Contenxt.Provider />에서 제공한 값을 사용할 수 있게 된다.
    • useContext는 상태관리를 위한 리액트의 API가 아니다. "상태를 주입해 주는 API다."
      • 상태관리 라이브러리는 어떠한 상태를 기반으로 다른 생태를 만들어 내거나 필요에 따라 상태 변화를 최적화할 수 있어야 한다.
    • Provider의 값이 변경이 되면 자식 컴포넌트 전체가 리렌더링이 일어난다. (리액트의 렌더링 동작)
    • 리렌더링을 막으려면 자식 컴포넌트에 React.memo를 달아주면 된다.
  • useReducer

    • useReducer의 세번째 인수는 useState의 게으른 초기화와 동일한 동작을 한다.
    • useReducer를 사용하는 목적은 복잡한 형태의 state를 사전에 정의된 dispatcher로만 수정할 수 있게 만들어서 state값에 대한 접근은 컴포넌트에서만 가능하게 한다.
    • state를 업데이트하는 방법은 컴포넌트 밖에다 둔다.
    • 장점은 sate값을 변경하는 시나리오를 제한적으로 두고 컴포넌트에서는 값에만 집중할 수 있게 해주고 state를 변경하고 싶을 때는 reducer만 바라볼 수 있게 해준다.

useState와 useReducer의 차이는 없다. useState는 preact에서 useReducer로 구현이 되어있다.
useReducer,useState둘다 결국 클로저를 활용해서 값을 가둬서 state를 관리한다.

export function useState(initialState) {
  currentHook = 1;
  return useReducer(invokeOrReturn, initialState);
}
export function useState(initialState) {
  currentHook = 1;
  return useReducer(invokeOrReturn, initialState);
}
  • useImperativeHandle

    • 부모에게서 넘겨받은 ref를 원하는 대로 수정할 수 있는 훅이다.
    • 자식 컴포넌트에서 부모에게 넘겨받은 ref에 새로운 값을 추가해줄 수 있다. (함수를 넘겨주거나 값을 넘겨줄 수 있음)
  • useLayoutEffect

    • 함수의 시그니처는 useEffect와 동일하나, 모든 DOM의 변경 후에 동기적으로 발생한다.

    • 함수의 시그니처가 비슷하다는 것은 두 훅의 형태나 사용 예제가 동일하다는것이다.

    • useLayoutEffects는 브라우저에 변경 사항이 반영되기 전에 실행이 되고, useEffect는 브라우저에 변경 사항이 반영된 이후에 실행된다.

    • 동작 과정

      1. 리액트가 DOM을 업데이트 (여기서 말하는 DOM이란 렌더링을 말하는 것이다. 브라우저에 실제로 해당 변경 사항이 반영(paint 단계)되는 시점이 아니다.)
      2. useLayoutEffect실행
      3. 브라우저에 변경 사항 반영
      4. useEffect 실행
    • "모든 DOM의 변경 후에 동기적으로 발생한다"의 뜻은 useLayoutEffect의 실행이 종료될 때까지 기다리고 화면을 그린다는 뜻이다.

      • 잘못 사용할 경우 성능에 문제가 생길수도 있다.
    • 어떻게 사용을 해야할까?

      • DOM은 계산됐지만 이것이 화면에 반영되기 전에 하고 싶은 작업이 있을 때 (스크롤 위치를 제어하는 등 화면에 반영되기 전에 하고 싶은 작업)

훅의 규칙

  • 최상위에서만 훅을 호출해야 한다. 반복문,조건문,중첩된 함수 내에서 훅을 실행할 수 없다.
  • 훅을 호출할 수 있는 것은 리액트 함수형 컴포넌트, 사용자 정의 훅 두 가지 경우뿐이다.

사용자 정의 훅과 고차 컴포넌트

  • 사용자 정의 훅

    • 서로 다른 컴포넌트 내부에서 같은 로직을 공유할 때 사용
    • 훅을 기반으로 개발자가 필요한 훅을 만드는 기법
    • 컴포넌트 전반에 걸쳐 동일한 로직으로 값을 제공하거나 특정한 훅의 작동을 취하게 하고 싶을때 사용하자
  • 고차 컴포넌트

    • 컴포넌트 자체의 로직을 재사용하기 위해 사용
    • 자바스크립트의 일급 객체, 함수의 특징을 이용하는 고차 함수의 일종이므로 자바스크립트 환경에서도 사용 가능
    • React.memo가 고차 컴포넌트의 일종이다.
    • 렌더링의 결과물에도 영향을 미치는 공통 로직이라면 고차 컴포넌트를 사용하자(고차 컴포넌트가 많으면 복잡성이 증가하므로 신중하게 사용하자)