React Hooks

Yun·2024년 7월 21일
0

개인공부

목록 보기
24/28

Hook

React Hooks은 use로 시작되는 함수이다. React 16.8에서 추가되었으며, 클래스 컴포넌트에서 사용하던 여러 기능을 함수형 컴포넌트에서도 사용할 수 있게 만들어 준다.

Hook의 규칙

Hook은 호출 위치에 제약이 있다.

최상위 레벨에서만 호출한다

  • 조건문이나 반복문 내부에선 호출하지 않는다.
  • return문 이후에 호출하지 않는다.
  • 이벤트 핸들러에서 호출하지 않는다.
  • 클래스 컴포넌트에서 호출하지 않는다.
  • useMemo, useReducer, useEffect에 전달된 함수 내부에서 호출하지 않는다.
  • try/catch/finally 블록 내부에서 호출하지 않는다.

React 함수 내에서만 호출한다

  • 일반적인 JavaScript 함수에서는 호출하지 않는다.
  • 함수형 컴포넌트나 커스텀 Hook에서는 가능하다.
// 아래는 불가능하다.
function setOnlineStatus() {
  const [onlineStatus, setOnlineStatus] = useOnlineStatus();
}

useState

useState는 상태를 관리하는 훅이다. 초기 상태 값과 상태를 업데이트하는 함수를 반환한다. 업데이트를 통해 상태를 변경할 수 있으며, 이 변경은 컴포넌트를 재렌더링한다.

function Counter() {
  const [count, setCount] = useState(0);
  
  function handleClick() {
    setCount(count + 1);
  }
  // ...

setReducer

setReducer는 더 복잡한 상태 관리를 위해 사용되는 훅이다. useState와 유사하게 상태와 상태 업데이트 함수를 반환하지만, 상태 업데이트 로직이 별도의 reducer 함수에서 처리되기 때문에 업데이트 로직이 복잡할 수록 유용하다.

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        Increment age
      </button>
      <p>Hello! You are {state.age}.</p>
    </>
  );
}

useContext

useContext는 컴포넌트 트리 전체에 데이터를 제공하는 context를 사용하게 해 주는 훅이다. 전역적인 데이터를 전달하기 좋지만, 컴포넌트를 재사용하기 어려워지기 때문에 꼭 필요할 때만 사용하자.

const ThemeContext = createContext('light');

export default function MyApp() {
  const [theme, setTheme] = useState('light');
  return (
    <>
      <ThemeContext.Provider value={theme}>
        <Form />
      </ThemeContext.Provider>
      <Button onClick={() => {
        setTheme(theme === 'dark' ? 'light' : 'dark');}} />
    </>
  )
}

function Form({ children }) {
  const theme = useContext(ThemeContext);
  return (
    //...

useRef

useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 훅이다. ref 변경은 재렌더링을 유발하지 않기 때문에, 시각적 출력에 영향을 미치지 않는 정보를 저장하는데 적합하다.

  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
  }

useId

useId는 고유한 ID를 생성하는 훅이다. React에서 ID를 직접 코드에 입력하는 건 좋은 사례가 아니다. 몇 번이고 렌더링되는 동안 ID는 고유해야 한다.

function PasswordField() {
  const passwordHintId = useId();
  return (
    <>
      <label>
        Password:
        <input
          type="password"
          aria-describedby={passwordHintId}
        />
      </label>
      <p id={passwordHintId}>
        The password should contain at least 18 characters
      </p>
    </>
  );
}

useEffect

useEffect는 컴포넌트가 리렌더링 될 때마다 특정 작업을 수행할 수 있게 하는 훅이다. 의존성 배열을 사용해 특정 객체의 상태 값이 변할 때마다 리렌더링 시킬 수 있다.

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);
  // ...
}

useMemo

useMemo는 재렌더링 사이에 계산 결과를 캐싱하는 훅이다. 성능 개선을 위해 사용한다. 비용이 많이 드는 계산을 수행하는 경우, 데이터가 변경되지 않았다면 계산을 생략하는 게 더 효율적이기 때문이다. 이것을 메모이제이션이라고 부른다.

  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  // ...
}
  • () => filterTodos(todos, tab)는 계산 함수이다.
  • [todos, tab]는 종속성 목록이다. 종속성이 변경되지 않았다면 이전에 이미 계산해둔 값을 반환하고, 변경되었다면 다시 연산한다.

useCallback

useCallback은 재렌더링 사이에 함수를 캐싱하는 훅이다. 성능 개선을 위해 사용한다.

function ProductPage({ productId, referrer, theme }) {
  function handleSubmit(orderDetails) {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }
  
  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

재렌더링 된 함수는 동일하지 않다. 같은 내용이라도 메모리 주소가 다르면 다른 함수로 간주하기 때문이다.

위의 코드의 경우, theme가 변경되면 재렌더링 되어 handleSubmit이 다른 함수가 된다. 때문에 <ShippingForm>도 불필요한 재렌더링을 피할 수 없게 된다.

function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);

  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

useCallback을 이용해 함수를 캐싱하면 불필요한 재렌더링을 피할 수 있다. handleSubmit이 변경되지 않기 때문에 <ShippingForm>이 재랜더링되지 않는다.

참고

0개의 댓글