React Hook에 대하여

연정·2023년 4월 9일
0

React

목록 보기
8/9
post-thumbnail

리액트는 개발을 돕는 여러가지 훅을 가지고 있다.
습관적으로 쓰기는 하지만, 그 훅에 대해서 얼마나 알고 있는지 의문이 들었다.
그래서 시작한 리액트 훅 파보기, 지금부터 시작!!

상태관리 Hooks

useState

useState는 리액트 개발할 때 가장 일반적이고 많이 쓰이는 훅인 것 같다.
컴포넌트 내에서 state를 추가하고 업데이트 하기 위해 사용하며, 값이 하나이거나 상태들이 서로 관련 없는 경우와 같이 간단한 상태관리가 필요할 때 사용한다.

useState의 초기 상태값으로는 number, string, boolean뿐 아니라 array, object, 심지어는 function도 전달 가능하다. 단, 초기 상태값으로 function을 전달할 경우에는 즉시 호출하면 매 랜더링 시마다 실행되므로 function 자체를 전달해야 한다.

const [state, setState] = useState(initialFunc())  -  X
const [state, setState] = useState(initialFunc)    -  O

useState를 사용할 때는 유의할 점이 몇 가지 있다.

1. State as a Snapshot
리액트는 랜더링 시점에 UI와 상태 등의 snapshot을 찍은 뒤 업데이트 사항을 DOM에 반영한다. 그렇기 때문에 리액트가 컴포넌트를 호출하고 나면, UI가 변경되기 전이라도 전달된 state를 변경할 수 없다. 리액트 공식 홈페이지에서 이해를 돕기 위한 귀여운 이미지를 함께 가져와보았다.

// 이 컴포넌트에서 number state는 +3이 아니라 +1로 업데이트 된다.
// snapshot이 찍힌 순간의 number는 0이기 때문!
// 이건 업데이트에 타임아웃을 걸더라도 동일하다.
// 왜냐하면 타임아웃이 호출된 시점의 snapshot을 기준으로 하기 때문이다.
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}


// 원하는대로 동작하게 하려면,
// 아래와 같이 순차적으로 진행되면서 이전 값을 참조하게 하면 된다!
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber((prev) => prev + 1);
        setNumber((prev) => prev + 1);
        setNumber((prev) => prev + 1);
      }}>+3</button>
    </>
  )
}

2. 함수형 프로그래밍의 원칙
함수형 프로그래밍의 핵심 원칙은 동일한 입력이 주어졌을 때 동일한 결과를 반환하는 것인데, useState를 사용할 때도 이를 주의하여야 한다.
예를 들어 아래 예시 코드와 같이 count를 업데이트하는 함수가 있다고 가정해보자.
여기서 첫 번째처럼 state를 직접 변경하게 될 경우, 결과를 예측하기 어렵고 언제나 동일한 결과가 나오는 것을 보장할 수 없게 된다. 그렇기 때문에 두 번째처럼 코드를 수정해야한다.

// 함수형 프로그래밍 원칙에 어긋나는 예시
// 외부 상태를 직접 변경
import React, { useState } from 'react';

function Counter () {
  const [counter, setCounter] = useState(0);
  
  function increment(){
  	count++;
    setCount(count);
  }
}

// 함수형 프로그래밍 원칙에 맞는 예시
// 들어오는 값이 동일하면 나오는 값도 동일
function Counter (){
  const [counter, setCounter] = useState(0);
  
  function increment(){
    setCount(prev => prev + 1);
  }
}

3. Automatic Batching
React v.18로 업데이트가 되면서 이벤트 핸들러 안에서만 적용되었던 automatic batching이 timeout, promise, native event handler 등에서도 적용되도록 확장되었다.
Batching은 리액트가 자체적으로 다수의 state의 업데이트를 묶어 처리함으로써 랜더링이 한 번만 이루어지도록 하는 것을 말한다. 이를 통해 성능의 개선을 달성할 수 있다.

4. Update queue
리액트에서 동일한 state를 여러번 수정할 때, 요청값은 모두 queue로 처리된다.
즉 처음에 요청된 것부터 순차적으로 처리된다는 것을 기억하자.
리액트 공식문서 - state queue

useReducer

useReducer도 state 업데이트를 위해 사용되는 훅이다.
우선 useReducer의 모습을 살펴보자 :)

import { useReducer } from 'react';

function reducer(state, action) {
	// 상태를 변경하는 함수
}

function Component(){
	const [state, dispatch] = useReducer(reducer, initialArg, init?);
}

useReducer의 인자

  • reducer 함수 : state를 어떻게 변경시킬지 결정하는 순수함수
  • initialArg : initial state가 계산된 값
  • init (optional) : 초기값을 반환하는 함수


    reducer 함수
  • 인자 : state & action
    • action 인자의 경우 일반적으로 type 프로퍼티를 보유한 객체의 형태
  • 두 가지 반환값
    • current state
    • dispatch function : state를 업데이트하고 re-render를 야기하는 함수

useReduceruseState는 비슷한 역할을 하지만, 복잡한 상태관리일 경우에는 useState보다는 useReducer를 쓰는 것이 더 효율적일 수 있다. 예를 들어 관련된 state가 여러 개인 경우, action의 type을 통해 각각에 따른 결과값을 reducer 내부에서 다르게 처리할 수 있기 때문에 useReducer가 훨씬 명시적으로 값을 처리할 수 있다.

function reducer(state, action){
	switch(action.type){
      case 'SET_PRODUCT':
        return {...state, products: action.payload};
      case 'set_CATEGORIES':
        return {...state, categories: action.payload};
      default:
        return state;
    }
}

또한 일반적으로 useReducerContext API와 함께 쓰이며, 여러 컴포넌트에서 상태가 공유되어야 할 때 사용한다고 한다.


메모이제이션 Hooks

useMemo

useMemo함수의 결과를 cache하기 위해 사용된다.

const cachedValue = useMemo(calculateValue, dependencies);

useMemo의 인자

  • calculateValue : 값을 결정하는 순수 함수. 따로 인자를 받지 않는다. () => {}
  • dependencies : dependencies 내부에 있는 값이 업데이트될 때에만 새로운 결과를 리턴한다.

리액트 공식문서에 따르면 useMemo는 expensive recalculation(1ms 이상)을 피하기 위해 사용된다고 한다. 처리가 오래 걸리는 함수의 경우 값이 꼭 업데이트 되어야 하는 상황이 아니라면, 캐싱된 값을 반환하여 성능을 개선하는 것!
다만, useMemo를 사용하게 되면 초기랜더링 시에는 캐시를 설정하고 값을 저장하는 과정이 추가되어 성능이 저하될 수 있고 re-rendering 시에만 성능 개선을 볼 수 있다는 점을 유의하자 :)

useCallback

useCallback함수 자체를 cache하기 위해 사용된다.

const cachedFn = useCallback(fn, dependencies);

useCallback의 인자

  • fn : 캐싱하고 싶은 함수로, 인자 전달이 가능하다. 초기랜더링 시 리액트는 함수를 반환(호출은 하지 않음)하며 dependencies 내부의 값이 변경되지 않는 한 동일한 함수를 반환한다.
  • dependencies

useCallback은 일반적으로 함수를 하위 컴포넌트로 넘겨줄 때 (props) 사용한다고 한다. 이를 통해 의도하지 않은 리랜더링을 방지할 수 있다.


위와 같이 useMemo & useCallback과 같은 훅을 사용하면 메모이제이션을 통해 리액트 랜더링 효율을 개선할 수 있다. 하지만 초기 랜더링 시간을 저하시킬 우려가 있고, 메모이제이션을 적용하는 공수도 들기 때문에 처음부터 메모이제이션을 적용하는 것은 권장되지 않는다고 한다. 우선 작업을 한 뒤, 눈에 띄게 속도가 느리거나 과한 리랜더링을 야기하는 부분이 있다면 해당 부분을 개선하는 것이 더 효율적일 것이다.


그 외의 Hooks

useEffect

useEffect는 컴포넌트가 마운트 / 언마운트 / 업데이트될 때 특정 작업을 처리하기 위해 사용하는 훅이다. 실제로 내가 코드를 작성할 때 너무도 쉽게 사용했던 훅이기도 하다.

useEffect(() => {
	// 해야하는 작업들
}, [dependencies])

하지만 아래의 아티클을 읽으면서 정말 많은 반성을 했다.
그동안 편하다는 이유만으로, 성능에 대한 고민 없이 useEffect를 너무 많이 써왔구나 싶었다.
모두들 아래의 아티클을 꼭꼭 읽어보기를 바란다.
useEffect 사용을 왜 그리고 어떻게 최소화할 수 있는지에 대한 이야기가 잘 나와있다.
https://react.dev/learn/you-might-not-need-an-effect

useRef

useRef는 랜더링이 필요하지 않은 값에 대한 참조를 할 수 있도록 돕는 훅이다.
값이 변경되는데도 UI 랜더링이 필요하지 않을 때 사용된다. (즉 화면에 영향을 주지 않는다.)

const ref = useRef(initialValue);

useRefcurrent라는 값 하나만을 리턴하는데, current는 ref에 저장된 값을 의미하며 새로운 값이 할당될 수 있다. 하지만 새로운 값이 할당되더라도 화면은 re-render되지 않는다.

일반적으로 useRef는 DOM을 조작하기 위해 가장 많이 쓰인다.


React v18에서 새롭게 등장한 Hooks

여러 훅이 새롭게 등장했지만, 그 중 실제로 쓰일만한 훅 3가지만 간단하게 소개하려고 한다.

useId

const id = useId();
  • unique ID를 생성하는 훅
  • label 등에 활용하여 휴먼에러를 줄이기 위해 활용

useTransition

const [isPending, startTransition] = useTransition();
  • UI를 막지 않으면서 state를 업데이트할 때 사용하는 훅
  • 시간 소요가 큰 컨텐츠를 로딩할 때 isPending을 활용하여 화면을 다르게 보여줄 수 있으며, startTransition으로 로딩이 완료된 후 특정 state를 변경하게끔 할 수 있다.
    (Suspense처럼 전체 화면을 block하지 않아도 됨!)

useDeferredValue

const deferredValue = useDeferredValue(value);
  • UI 일부의 업데이트를 미룰 수 있는 훅
  • 새로운 컨텐츠가 로딩중일 때 과거 컨텐츠를 보여준다

새롭게 등장한 훅은 여러 상황에 적용해보면서 어떤 상황에 가장 적합한지 사용해봐야 할 것 같다. 일단 오늘의 스터디는 여기까지!!!
profile
성장형 프론트엔드 개발자

0개의 댓글