useReducer Hook

G-NOTE·2021년 6월 8일
0

React

목록 보기
8/27

useReducer : 상태를 업데이트하는 Hook

useState와의 차이점

useState

setValue(5);
  • 설정하고 싶은 다음 상태를 직접 지정하는 방식으로 상태 업데이트

useReducer

const [state, dispatch] = useReducer(reducer, initialState);
  • Action 객체를 기반으로 상태를 업데이트한다.

  • Action 객체 : 상태를 업데이트 할 때 참조하는 객체

  • type을 사용해서 어떤 업데이트를 진행할 것인지 명시할 수 있고, 업데이트할 때 참조할 다른 값을(diff) 객체 안에 넣을 수 있다.

  • 만약 컴포넌트에서 관리하는 값이 1개이고 그 값이 단순하다면(문자열, 숫자, 불리언 등) useState로 관리하는 것이 용이하다.

  • 하지만 컴포넌트에서 관리하는 값이 여러 개고 상태 구조가 복잡해진다면 useReducer가 더 편리할 것이다.

useReducer의 장점

  • 상태 업데이트 로직을 컴포넌트 밖으로 분리할 수 있다. (아예 파일을 분리할 수도 있다.)

useReducer 개념

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT': 
      return state + 1;
    case 'DECREMENT': 
      return state - 1;
    default:
      return state;
  }
}

const [number, dispatch] = useReducer(reducer, 0);
  • reducer : 상태를 업데이트하는 함수
  • 현재 상태(state)와 action 객체를 받아서 업데이트된 상태를 반환하는 함수
  • number : 현재 상태
  • dispatch : 액션을 발생시키는 객체
  • useReducer(reducer, 0); : reducer(reducer 함수), 0(초기값)

Counter 예제

import { useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      throw new Error("unhandled action");
  }
}

function Counter() {
  const [number, dispatch] = useReducer(reducer, 0);

  const onIncrease = () => {
    dispatch({
      type: "INCREMENT",
    });
  };

  const onDecrease = () => {
    dispatch({
      type: "DECREMENT",
    });
  };

  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

기존 useState를 useReducer로 변경하기

App.js

import { useRef, useReducer, useMemo, useCallback } from "react";
import UserList from "./UserList";
import CreateUser from "./CreateUser";

function countActiveUsers(users) {
  console.log("counting active users...");
  return users.filter((user) => user.active).length;
}

const initialState = {
  inputs: {
    username: "",
    age: "",
  },
  users: [
    { id: 1, username: "bae", age: 22, active: true },
    { id: 2, username: "lee", age: 21, active: false },
    { id: 3, username: "lim", age: 25, active: false },
  ],
};

function reducer(state, action) {
  switch (action.type) {
    case "CHANGE_INPUT":
      return {
        // 기존 상태를 넣고 inputs 값을 덮어씌워서 불변성을 유지한다.
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value,
        },
      };
    case "CREATE_USER":
      return {
        // 작성된 내용 input에서 삭제
        inputs: initialState.inputs,
        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:
      throw new Error("Unhandled action");
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { users } = state;
  const { username, age } = state.inputs;
  const nextId = useRef(4);

  const onChange = useCallback((e) => {
    const { name, value } = e.target;
    dispatch({
      type: "CHANGE_INPUT",
      name,
      value,
    });
  }, []);

  const onCreate = useCallback(() => {
    dispatch({
      type: "CREATE_USER",
      user: {
        id: nextId.current,
        username,
        age,
      },
    });
    nextId.current += 1;
  }, [username, age]);

  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}
        age={age}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성 사용자 수 : {count}</div>
    </>
  );
}
export default App;
  • 기존 useState로 관리되던 inputs, users를 App 함수 밖으로 빼낸다.
  • onChange 함수 : 컴포넌트가 처음 렌더링될 때 한번 만들어지고 이후 계속 재사용
profile
FE Developer

0개의 댓글