Dev/React

모던 리액트 Deep Dive 3장

takeU 2023. 12. 28. 14:37
반응형

3. 리액트 훅 깊게 살펴보기

3.1. 리액트의 모든 훅 파헤치기

3.1.1. useState

함수형 컴포넌트 내부에서 상태를 정의하고, 관리할 수 있게 해주는 훅

const [state, setState] = useState(initialState)
  • useState의 초깃값에 함수를 넣는 것 - 게으른 초기화 (lazy initialization)
    • 게으른 초기화 함수는 state가 처음 만들어질 때만 실행
    • 초깃값이 복잡하거나 무거운 연산을 포함할 때 사용

3.1.2. useEffect

애플리케이션 내 컴포넌트의 여러 값들을 활용해 동기적으로 부수 효과를 만드는 매커니즘

function Component() {
    useEffect(() => {
        // code
    }, [props, state])

첫 번째 인수 - 실행할 부수 효과가 포함된 함수

두 번째 인수 - 의존성 배열

렌더링 시점을 기준으로 의존성에 있는 값이 변경되면 부수 효과를 실행하는 원리

[Object.is](http://Object.is) 기반의 얕은 비교를 수행

클린업 함수

  • 새로운 값을 기반으로 렌더링 뒤에 실행되지만, 함수가 정의됐을 당시에 선언됐던 이전 값을 보고 실행
  • 즉, 렌더링 시 선언된 값을 보여줌

주의점

  • eslint-disable-line react-hooks/exhaustive-deps 주석 사용 지양
    • 의존성에 빈 배열이 들어가는 경우 eslint 경고가 뜨는데 이를 막아주는 주석
    • 일반적으로 빈 배열을 넣는 상황은 만들지 않는것이 좋음
  • useEffect의 첫 번째 인수에 함수명을 부여하기
    • 적절한 기명을 통해 쓰임을 알게하는것이 좋음
  • 거대한 useEffect 만들기 지양
    • 작은 의존성 배열을 사용하는 여러개의 useEffect로 분리
    • 부득이하게 사용하게 된다면 useCallback, useMemo 등으로 사전에 정제한 내용만 담는 것이 좋음
  • 불필요한 외부함수 만들기 지양
    • 콜백 함수로 비동기 함수를 바로 넣지 않는 이유 - useEffect의 race condition 방지
    • 내부에서 비동기 함수를 선언해 실행하거나, 즉시실행함수를 만드는 것은 가능
    • 사용 시, 클린 업 함수에서 비동기 함수에 대한 추가적인 처리를 해주는 것이 좋음 (반복생성, 실행 방지)

3.1.3. useMemo

비용이 큰 연산에 대한 결과를 저장해 두고, 저장된 값을 반환하는 훅

const memo = useMemo(() => expensiveComputation(a, b), [a, b])

의존성이 변경되지 않으면 함수를 재실행 하지 않음

단순한 값 뿐만 아니라, 컴포넌트도 가능 (굳이..)

3.1.4. useCallback

useMemo는 값을 기억, useCallback은 콜백 자체를 기억

의존성이 변경되었을 때만 함수가 재생성

3.1.5. useRef

useState와 마찬가지로 컴포넌트 내부에서 렌더링이 일어나도 변경 가능한 상태값을 저장

current로 값에 접근 또는 변경할 수 있음

값이 변하더라도 렌더링이 발생하지 않음

일반적으로 DOM에 접근할 때 사용 (input, button 등 ..)

useMemo를 통해 구현이 가능함

3.1.6. useContext

props drilling을 극복하기 위해 등장

<Provider>에서 값을 가져오며, 여러개의 Provider가 있다면, 가장 가까운 Provider 값을 가져옴

컴포넌트 내부에서 사용 시, 컴포넌트 재활용이 어려워짐

Context는 상태관리용이 아닌 단순히 상태를 주입하는 기능만 있음 → 렌더링 최적화에는 별 도움이 되지 않음 → memo와 함께 사용하면 개선이 가능

3.1.7. useReducer

useState의 심화 버전이라 볼 수 있음

const [state, dispatcher] = useReducer(reducer, initialState, init)

// state - 상태값
// dispatcher - state 업데이트에 사용되는 함수
// reducer - state가 어떻게 변경될지 정의
// initialState - 초깃값
// init - initialState 초기화 함수

복잡한 형태의 state를 사전에 정의된 dispatcher로만 수정이 가능하도록 만듦

내부적으로 useStateuseReducer로 구현돼 있음

3.1.8. useImperativeHandle

부모에게 넘겨 받은 ref에 추가적인 동작을 정의할 수 있는 훅

3.1.9. useLayoutEffect

모든 DOM의 변경 후(렌더링)에 동기적으로 발생하는 훅

useEffect와 시그니처가 동일함 (훅의 형태)

실행 순서는 다음과 같다

  1. DOM 업데이트
  2. useLayoutEffect 실행
  3. 브라우저에 변경 사항 반영
  4. useEffect 실행

동기적으로 진행되기 때문에 컴포넌트가 중지되는것과 같은 일이 발생할 수 있음

따라서 DOM은 계산됐지만 화면이 반영되기 전에 하고 싶은 작업이 있을 때 사용해야 함

  • 애니메이션
  • 스크롤 제어

이를 통해 자연스러운 사용자 경험을 제공할 수 있음

3.1.10. useDebugValue

디버깅에 사용

훅 내부에서만 실행 가능하며 두 번째 인수로 넘어간 포매팅 함수는 첫 번쨰 인자가 변경되었을때 실행되어 값을 노출함

3.1.11. 훅의 규칙

  1. 최상위에서만 훅을 호출해야 함. 반복문, 조건문, 중첩된 함수 내에서 실행할 수 없음
    1. 훅은 리액트 어딘가에 있는 index와 같은 키 기반으로 구현돼 있음 (객체 기반 링크드 리스트에 더 가까움) → 파이버 객체의 링크드 리스트 호출 순서에 따라 저장됨
    2. 순서를 보장 받을 수 없는 상황이 온다면 에러가 발생
  2. 훅을 호출할 수 있는 것은 리액트 함수형 컴포넌트, 혹은 사용자 정의 훅 두가지 뿐

3.2. 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?

재사용 로직을 관리할 수 있는 두 가지 방법

3.2.1. 사용자 정의 훅

서로 다른 컴포넌트 내부에서 같은 로직을 공유하고자 할 때 사용

use 를 접두사로 사용해야 훅이라 인식

3.2.2. 고차 컴포넌트

Higher Order Component / 고차 컴포넌트 기법으로 최적화, 중복 로직 관리에 사용

React.memo - 가장 널리 쓰이는 고차 컴포넌트

props가 변경되었을 때만 실행되도록 하는 역할 → 불필요한 렌더링 생략

with 를 접두사로 사용해야 고차 컴포넌트라 인식

부수효과를 최소화로 해야 하며 중첩해 사용하는 것 또한 최소화 해야 함

3.2.3. 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?

사용자 정의 훅이 필요한 경우

  • 단순히 리액트 훅으로만 공통 로직을 분리할 수 있는 경우
  • 사용자 정의 훅 자체로는 렌더링에 영향을 미치지 못하기 때문에, 컴포넌트 내부에 미치는 영향을 최소화해 개발자가 훅을 원하는 방향으로 사용할 수 있음
  • 부수효과가 비교적 제한적

⇒ 단순히 컴포넌트 전반에 걸쳐 동일한 로직으로 값을 제공하거나 특정한 훅의 작동을 취하게 하고 싶을 때 사용

고차 컴포넌트가 필요한 경우

  • 렌더링 결과물에도 영향을 미치는 공통로직인 경우 고차 컴포넌트 사용

출처: 모던 리액트 Deep Dive