본문 바로가기

React/Velopert's React

[React] Study #20 | 커스텀 Hooks 만들기

커스텀 Hook은 언제 필요?

  • 컴포넌트를 만들다보면, 반복되는 로직이 자주 발생
  • input을 관리하는 코드는 관리 할 떄마다 꽤나 비슷한 코드가 반복

이번 포스팅 내용은, 위의 상황에 커스텀 Hooks를 만들어서 반복되는 로직을 쉽게 재사용하는 방법을 알아보는 것이다.

 

커스텀 Hooks 만드는 방법

  1. src 디렉터리에 hooks라는 디렉터리를 만들고, 그안에 useInputs.js 파일을 만든다
  2. 커스텀 Hook을 만들대는 보통 이렇게 use 라는 키워드로 시작하는 파일을 만들고 그안에 함수를 작성한다.
  3. 파일안에 useState, useEffect, useReducer, useCallback등 Hooks를 사용하여 원하는 기능을 구현해주고 컴포넌트에서 사용하고 싶은 값들을 반환해주면 된다.

useInputs.js

import { useState, useCallback } from 'react';

function useInputs(initialForm) {
  const [form, setForm] = useState(initialForm);
  // change
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setForm(form => ({ ...form, [name]: value }));
  }, []);
  const reset = useCallback(() => setForm(initialForm), [initialForm]);
  return [form, onChange, reset];
}

export default useInputs;

이제, 이 useInputs Hook을 App.js에서 사용해보자 

기존, App.js 에서 useReducer 쪽에서 사용하는 inputs를 없애고 이에 관련된 작업을 useInputs으로 대체해준다.

새로운 항목을 추가할 때 input 값을 초기화 해야 하므로 데이터 등록 후, reset()을 호출해준다!

 

App.js

import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs';

function countActiveUsers(users){
  console.log('활성 사용자 수를 세는 중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: 
  [
    {id: 1, username: 'John', email: 'john@example.com',
    active: true,
  },
    {id: 2, username: 'Car', email: 'car@example.com',
    active: false,
  },
    {id: 3, username: 'lee', email: 'lee@example.com',
    active: false,
  },
  ]
}

// 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수
function reducer(state, action){
  switch (action.type) {
    case 'CREATE_USER':
      return{
        users: state.users.concat(action.user),
      };
    case 'TOGGLE_USER':
      return{
        ...state,
        users: state.users.map(user=>
          user.id === action.id ? {...user, active: !user.active}: user)
      };
    case 'REMOVE_USER':
      return{
        ...state,
        users: state.users.filter(user => user.id != action.id)
      };
    default:
      return state;
  }; 
}

function App() {
  const [{username, email}, onChange, reset] = useInputs({
    username: '', email: ''
  })


  const [state, dispatch] = useReducer(reducer, initialState);

  const nextId = useRef(4);

  const { users } = state;
  
  const onCreate = useCallback(()=>{
    dispatch({
      type: 'CREATE_USER',
      user: {
        id: nextId.current,
        username,
        email,
      }
    });
    nextId.current += 1;

    // 새로운 항목을 추가할 때 input 값을 초기화해야하므로 데이터 등록 후 reset()을 호출 
  },[username, email, reset]);

  const onToggle = useCallback((id)=>{
    dispatch({
      type: 'TOGGLE_USER',
      id
    })
  },[])

  const onRemove = useCallback((id)=>{
    dispatch({
      type: 'REMOVE_USER',
      id
    })
  },[])

  const count = useMemo(()=> countActiveUsers(users), [users]);
  return (   
    <>
      <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/>
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성 사용자 수 : {count} </div>
    </>
  );

}
  

export default App;

이렇게 커스텀 Hooks를 만들어서 사용하면 컴포넌트의 로직을 분리시켜서 필요할 떄 쉽게 재사용할 수도 있다.

 


➕useInputs 커스텀 Hook을 useReducer 을 사용해서 구현해보기

import { useReducer, useCallback } from 'react';


function reducer(state, action){
    switch(action.type) {
        case 'CHANGE':
            return{
                ...state,
                [action.name]: action.value,
            };
        case 'RESET':
            return Object.keys(state).reduce((acc, current) => {
                acc[current] = '';
                return acc;
              }, {});
        default:
            return state;
    }
}

function useInputs(initialForm) {

  const [form, dispatch] = useReducer(reducer, initialForm);


  // change
  const onChange = useCallback(e => {
    const {name, value} = e.target;
    dispatch({
        type: 'CHANGE',
        name,
        value
    })
  }, []);

  const reset = useCallback(() => {
    dispatch({
        type: 'RESET'
    })
  },[initialForm]);
  return [form, onChange, reset];
}

export default useInputs;

 

 


Reference

https://react.vlpt.us/basic/21-custom-hook.html