전체 방문자
오늘
어제
  • 전체 글
    • HTML
    • CSS
    • Javascript
    • React
    • Typescript
    • Next.js
    • Webpack
    • Vue.js
    • Git & GitHub
    • Error
    • Study
    • 개발 일지✨

블로그 메뉴

  • 💡
  • ⚙️
hELLO · Designed By 정상우.
하루

Home

Custom Hooks 만들기
React

Custom Hooks 만들기

2022. 5. 12. 17:29

커스텀 훅을 만들어서 반복되는 로직을 쉽게 재사용할 수 있다.

커스텀 훅은 파일을 따로 만들어서 그 안에 함수를 작성해서 사용한다.

useState, useEffect, useReducer, useCallback 등 Hooks 를 사용하여 원하는 기능을 구현하고, 컴포넌트에서 사용하고 싶은 값들을 반환한다.

 

예시

함수에서 관리할 form 에 대하여 초기값을 파라미터(initialForm)로 받아오고, form 을 통해 상태도 조회할 수 있다.

onChange 를 통해 변경되는 이벤트를 관리한다. 초기화하고 싶다면 reset 을 호출하면 된다.

 

useInputs.js

function useInputs(initialForm) {
    const [form, setForm] = useState(initialForm); // 1
    const onChange = useCallback(e => { // 2
        const { name, value } = e.target;
        setForm(form => ({ ...form, [name]:value })); // 3
    }, []) // 4
    const reset = useCallback(() => setForm(initialForm), [initialForm]); // 5

    return [form, onChange, reset]; // 6
}

export default useInputs;

1. form 이라는 새로운 상태를 선언하는데, 상태의 초기값 initialForm 을 useInputs 의 파라미터로 받아온다.

2. onChange 함수는 useCallback 을 이용해서 e 이벤트 파라미터를 가져온 뒤, name 과 value 를 e.target 에서 추출한다.

3. setForm 에서는 ...form 을 통해 기존의 form 을 복사해준 뒤 업데이트 해주고 [name]: value 로 설정해준다.

더보기
setForm(form => ({ ...form, [name]: value }));

참고

** ES5 문법: 화살표 함수

객체 리터럴 표현을 반환하기 위해서는 함수 본문(body)을 괄호 속에 넣어준다.

params => ({ foo:bar });

따라서 위에 괄호 ( ) 로 감싸준 부분을 풀어서 쓰면 다음과 같이 이해할 수 있다.

setForm( (form) => function () { return {...form, [name]: value}; });

4. 그리고 전체 useInputs 의 deps 배열은 의존하는 상태가 없으므로 비워주었다.

5. form 을 초기화시켜주는 reset 함수를 생성한다. 초기값을 받아온 것으로 설정해준다. dpes 배열에는 파라미터로 받아온 initialForm 을 배열에 넣어준다.

6. 생성한 함수들을 return 반환을 통해 내보내줄 수 있도록 한다.

 

App 함수에서 useInputs 객체를 불러와서 초기값을 설정해주었다.

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

form 으로부터 username 과 email 을 추출해준다. (비구조화 할당)

const { usernmae, email } = form;

 

App.js

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
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: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return {
        users: state.users.concat(action.user)
      };
    case 'TOGGLE_USER':
      return {
        users: state.users.map(user =>
          user.id === action.id ? { ...user, active: !user.active } : user
        )
      };
    case 'REMOVE_USER':
      return {
        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
      }
    });
    reset();
    nextId.current += 1;
  }, [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;

 

useReducer 로 구현해보기

기존 useInputs.js

function useInputs(initialForm) {
    const [form, setForm] = useState(initialForm);
    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;

 

useReducer로 구현한 useInputs.js

import { useCallback, useReducer } from "react";

// useInputs의 set 함수를 useReducer 로 구현하기
// 1. reducer 생성하기 (일단 return state;)
// 2. useReducer 함수 작성하기
// 3. dispatch 액션 설정
// 4. reducer 함수에 액션 타입별 작동코드 설정

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" }), []);
    return [form, onChange, reset];
}

export default useInputs;

 

    'React' 카테고리의 다른 글
    • Immer 로 불변성 관리하기
    • Context API를 사용한 전역값 관리
    • useReducer
    • 함수형 업데이트 useState

    티스토리툴바