Front-end/React

[React] useReducer

냥인 2021. 6. 7. 18:47
728x90
반응형

useReducer

리액트의 컴포넌트에서 상태를 업데이트 하기 위해서는 useState를 사용해서 상태(state)를 관리했었습니다.

그런데 useState 말고도 상태를 관리할 수 있는 방법이 있는데 바로 useReducer 입니다.

 

useState와 useReducer의 차이?

useReducer를 사용하면 컴포넌트 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있습니다.

상태 업데이트 로직을 컴포넌트 바깥에 작성할 수 있을 뿐만 아니라 심지어는 아예 다른 파일에 작성한 후 불러와서 사용할 수도 있습니다.

 

import React, { useState } from 'react';

function Counter() {
  const [number, setNumber] = useState(0);

  const onIncrease = () => {
    setNumber(prevNumber => prevNumber + 1);
  };

  const onDecrease = () => {
    setNumber(prevNumber => prevNumber - 1);
  };

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

export default Counter;

✅Counter.js

이것은 기존의 useState를 이용해서 상태를 관리했었던 Counter 컴포넌트입니다.

이것을 useReducer로 변경해보는 것을 지금부터 해보겠습니다.

 

useReducer 알아보기

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

useReducer는 이런 식으로 사용하면 되는데요.

- state : 우리가 앞으로 컴포넌트에서 사용할 수 있는 상태를 가르킴

- dispatch : 액션을 발생시키는 함수, dispatch는 dispatch({type : 'INCREMENT' }) 이런 식으로 사용함

- useReducer의 첫번째 파라미터 : reducer 함수

- useReducer의 두번째 파라미터 : state의 초기 값

 

reducer 함수 작성하기

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

위와 같이 reducer 함수를 컴포넌트와 분리하여 작성할 수 있습니다.

 

useReducer를 사용하도록 고친 Counter.js

import React, { useReducer } from "react";

const 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;

 


useReducer를 이용하여  함수 만들어보기

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

function countActiveUsers(users) {
  return users.filter((user) => user.active).length;
}
const initialState = {
  inputs: {
    username: "",
    email: "",
  },
  users: [
    {
      id: 1,
      username: "velopert",
      email: "public.velopert@gmail.com",
      active: false,
    },
    {
      id: 2,
      username: "tester",
      email: "tester@example.com",
      active: false,
    },
    {
      id: 3,
      username: "liz",
      email: "liz@example.com",
      active: false,
    },
  ],
};
const reducer = (state, action) => {
  switch (action.type) {
    case "CHANGE_INPUT":
      return {
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value,
        },
      };
    case "CREATE_USER":
      return {
        inputs: initialState.inputs,
        users: state.users.concat(action.user),
      };
    case "REMOVE_USER":
      return {
        ...state,
        users: state.users.filter((user) => user.id !== action.id),
      };
    case "TOGGLE_USER":
      return {
        ...state,
        users: state.users.map((user) =>
          user.id === action.id ? { ...user, active: !user.active } : user
        ),
      };
    default:
      throw new Error("Unhandled action");
  }
};
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);
  const {
    inputs: { username, email },
    users,
  } = state;

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

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

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

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

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

export default App;

만든 함수들은 CreateUser 컴포넌트나 UserList 컴포넌트로 전달이 되는데 App 컴포넌트에서 만들고 계속 재사용하면 되기 때문에 useCallback을 사용해주었습니다.

 

어떨때 useReducer를 쓰고 어떨 때 useState를 써야할까?

정해진 답은 없으며 상황에 따라 다릅니다.

예를 들어 컴포넌트에서 관리하는 값이 1개이고 그 값이 단순히 숫자, 문자열 등등 이라면 useState로 관리하는 것이 더 편할 것입니다.

그러나 컴포넌트에서 관리해야할 값이 많아져서 상태의 구조가 복잡해지거나 한다면 useReducer가 좀 더 편할 수 있습니다.

쉽게 생각한다면 간단한거면 useState를 사용하고 복잡하다면 useReducer를 쓰는 경우가 많은 것 같습니다.

 

 

728x90
반응형