React- Custom Hook

박요셉·2024년 5월 29일
0

React

목록 보기
10/15

커스텀 훅이란?

리액트는 제공해주지 않지만, 개발을 진행할 때 개인적으로 'hook'으로 만들면 더 편하겠다고 느낄 때, 자신만의 hook으로 만드는 것을 의미한다.
즉, 같은 로직이 반복될 때 두 로직을 나누어서 작성하는 게 가능할지라도, 굉장히 비효율적이다. 이러한 로직을 하나의 커스텀훅 으로 따로 정의하여 비효율성을 낮출 수 있다.
커스텀 훅은 컴포넌트 분할과는 달리, 컴포넌트 로직 자체를 분할 + 재사용 할 수 있게 하는 것이다.

custom hook 규칙

  1. use로 시작해야 한다.

    리액트에서 hook들은 모두 use로 시작하는 것이 원칙이고, custom hook 또한 예외가 될 수 없다. use뒤 대문자로 시작하는 것 또한 지켜주어야 한다.
    따라서 커스텀훅이 아닌 일반 함수를 정의할 때에는 use로 시작하는 것을 지양해주어야 한다.

  2. state 자체가 아닌 state 저장 논리를 공유하는 것

    hook에 대한 각 호출은 동일한 hook에 대해 모든 호출이 독립적이다.
    즉, 같은 hook이 연이어 호출되었다고 해도 서로의 기능에는 영향을 미치지 않는다.
    한마디로 잘 작동한다. 모두 독립적인 로직 자체만을 공유하는 것이기 때문!

  3. custom hook은 순수 함수로 기대된다.

    custom hook은 컴포넌트가 리렌더링되면 함께 리렌더링 된다.
    커스텀 훅은 늘 최신의 props와 state를 받기 때문이다.

  4. 최상위에서만, React 함수 내에서만 호출해야 한다.


    리액트에서는 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장되어야 한다라고 말한다. 즉, hook을 조건문이나 일반 함수에서 사용하게 되면, 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 호출이 보장되는 것에 대한 약속이 깨지는 결과가 일어난다.

  5. CustomHook에 함수 넘겨주기

    함수를 custom hook에게 넘겨주는 형태로 변환할 수 있다.

언제 사용?

모든 로직이 겹친다고 하나하나 커스텀 훅으로 뺄 필요는 없다.
그렇지만, Effect에 관한 부분이 있다면, 커스텀훅으로 감싸주는 것이 더 낫다.
Effect를 남용해서는 안되기 때문,
커스텀 훅으로 감쌀 때, 더욱 명확하게 결과를 보여주는 경우가 있다.
근데 나는 겹치면 일단 훅으로 추출 해버릴듯...?

Custom Hook의 장점

  1. Effect와 데이터 흐름을 명확하게 만들 수 있다.
  2. 컴포넌트가 자신의 역할에 집중할 수 있다.
  3. 리액트가 새로운 기능을 추가했을 시, 컴포넌트 요소를 바꾸지 않고도 해당 effect를 제거할 수 있다.

예시

기본 상태 업데이트 로직

import React, { useState } from 'react';

const initialState = {
  todos: [
    { id: 1, text: 'Learn React', done: false },
    { id: 2, text: 'Practice React', done: false }
  ]
};

function App() {
  const [state, setState] = useState(initialState);

  const toggleTodo = (id) => {
    const updatedTodos = state.todos.map(todo =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    );
    setState({ ...state, todos: updatedTodos });
  };

  return (
    <div>
      {state.todos.map(todo => (
        <div key={todo.id}>
          <span>{todo.text}</span>
          <button onClick={() => toggleTodo(todo.id)}>
            {todo.done ? 'Undo' : 'Complete'}
          </button>
        </div>
      ))}
    </div>
  );
}

export default App;

커스텀 훅으로 분리

import React, { useState, useCallback } from 'react';

// 커스텀 훅 정의
const useCustomState = (initialState) => {
  const [state, setState] = useState(initialState);

  const updateState = useCallback((updater) => {
    setState(prevState => {
      const nextState = { ...prevState };
      updater(nextState);
      return nextState;
    });
  }, []);

  return [state, updateState];
};

// App 컴포넌트
function App() {
  const [state, updateState] = useCustomState({
    todos: [
      { id: 1, text: 'Learn React', done: false },
      { id: 2, text: 'Practice React', done: false }
    ]
  });

  const toggleTodo = (id) => {
    updateState(nextState => {
      const todo = nextState.todos.find(todo => todo.id === id);
      if (todo) {
        todo.done = !todo.done;
      }
    });
  };

  return (
    <div>
      {state.todos.map(todo => (
        <div key={todo.id}>
          <span>{todo.text}</span>
          <button onClick={() => toggleTodo(todo.id)}>
            {todo.done ? 'Undo' : 'Complete'}
          </button>
        </div>
      ))}
    </div>
  );
}

export default App;

updateState 함수를 반환해줘서 state의 관리에 쓰는 걸 볼 수 이씀

다양한 상태 관리와 API 호출

import React, { useState, useEffect, useCallback } from 'react';

// API 호출 및 상태 관리를 위한 커스텀 훅
const useTodos = (initialTodos) => {
  const [todos, setTodos] = useState(initialTodos);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchTodos = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch('/api/todos');
      const data = await response.json();
      setTodos(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);

  const toggleTodo = useCallback((id) => {
    setTodos(prevTodos => 
      prevTodos.map(todo => 
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    );
  }, []);

  useEffect(() => {
    fetchTodos();
  }, [fetchTodos]);

  return { todos, loading, error, toggleTodo, fetchTodos };
};

// App 컴포넌트
function App() {
  const { todos, loading, error, toggleTodo, fetchTodos } = useTodos([]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <button onClick={fetchTodos}>Refresh Todos</button>
      {todos.map(todo => (
        <div key={todo.id}>
          <span>{todo.text}</span>
          <button onClick={() => toggleTodo(todo.id)}>
            {todo.done ? 'Undo' : 'Complete'}
          </button>
        </div>
      ))}
    </div>
  );
}

export default App;

한 hook에서 많은 비즈니스 로직들을 수행하고 있지만 예시를 위한 것이지 따로 추출해내도 된다.

profile
개발자 지망생

0개의 댓글