[React] 커스텀 Hook

sue·2021년 1월 18일
0

react note

목록 보기
11/17

컴포넌트를 만들다 보면 반복되는 로직이 발생한다. (ex. input 관리)
이런 경우 커스텀 Hooks를 만들어서 반복되는 로직을 재사용할 수 있다.

useInputs.js

import { useState, useReducer, useCallback } from "react";

function reducer(state, action) {
  // CHANGE
  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;
    // RESET
  }
}

function useInputs(initialForm) {
  // 해당 input 폼에서 관리할 초기값
  // const [form, setForm] = useState(initialForm);

  const [form, dispatch] = useReducer(reducer, initialState);
  const onChange = useCallback((e) => {
    const { name, value } = e.target;
    dispatch({
      type: "CHANGE",
      name,
      value,
    });
  });

  const reset = useCallback(() => {
    dispatch({
      type: "RESET",
    });
  }, []);

  /*
  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

import React, { useReducer, useRef, useMemo, useCallback } from "react";
import CreateUser from "./CreateUser";
import UserList from "./UserList";
import useInputs from "./useInputs";
// useMemo: 특정값이 바뀌었을 때만 특정 함수를 실행해서 연산하도록 처리

function countActiveUsers(users) {
  console.log("활성 사용자 수 세는 중");
  return users.filter((user) => user.active).length;
  // active가 true인 사용자 필터링해 수를 연산해서 가져옴
}

// 1) 앱 컴포넌트에서 사용할 초기상태를 컴포넌트 바깥에 선언해주기
const initialState = {
  users: [
    {
      id: 1,
      username: "su",
      email: "susu@gmail.com",
      active: true,
    },
    {
      id: 2,
      username: "liz",
      email: "lili@gmail.com",
      active: false,
    },
    {
      id: 3,
      username: "ro",
      email: "ro@gmail.com",
      active: false,
    },
  ],
};

// 2) reducer 함수의 틀 만들기
function reducer(state, action) {
  switch (action.type) {
    case "CREATE_USER":
      return {
        inputs: initialState.inputs, // input 공백값 반환
        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() {
  // 3) 내부에서 useReducer 함수 선언
  const [state, dispatch] = useReducer(reducer, initialState);
  const [form, onChange, reset] = useInputs({
    username: "",
    email: "",
  });
  const { username, email } = form;

  // 6-1) useRef 사용 아이디값 부여
  const nextId = useRef(4);

  // 4) 비구조화 할당으로 users, inputs 내용 추출
  const { users } = state;

  // 5) onChange 함수 구현

  // 6) onCreate 구현
  const onCreate = useCallback(() => {
    dispatch({
      type: "CREATE_USER",
      user: {
        id: nextId.current,
        username,
        email,
      },
    });
    nextId.current += 1;
    reset();
  }, [username, email, reset]); // 함수에서 사용하는 상태 혹은 props, deps 배열에 포함

  // 7) onToggle
  const onToggle = useCallback((id) => {
    dispatch({
      type: "TOGGLE_USER",
      id,
    });
  }, []);

  // 8) onRemove
  const onRemove = useCallback((id) => {
    dispatch({
      type: "REMOVE_USER",
      id, // id값만 비교해주니까!
    });
  }, []);

  // 9) 활성사용자 수
  const count = useMemo(() => countActiveUsers(users), [users]);

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

export default App;

0개의 댓글