본문 바로가기

React/이론공부

[React Hooks] useReducer

useReducer 란

- state(상태) 를 관리하고 업데이트 하는 hook 인 useState 를 대체할 수 있는 Hook

- useState 처럼 state를 관리하고 업데이트 할 수 있는 hook

useReducer 의 매력

- 한 컴포넌트 내에서 state 를 업데이트 하는 로직 부분을 그 컴포넌트로부터 분리시키는 것을 가능하게 해줌
=> state 업데이트 로직을 분리하며 컴포넌트 외부에 작성하는 것을 가능하게 함으로써, 코드의 최적화를 이루게 해줌.

 

useReducer 와 useState 의 사용 차이

 

useState

- 관리해야 할 state 가 1개일 경우

- state가 단순한 숫자, 문자열 또는 Boolean 값일 경우

 

useReducer

- 관리해야 할 state 가 1개 이상, 복수일 경우

- 현재는 단일 state 값을 관리하지만, 추후 유동적일 가능성이 있을 경우

- 스케일이 큰 프로젝트의 경우

- state 의 구조가 복잡해질 것으로 보이는 경우

 

useReducer 에서 알아야 할 4가지

 

useReducer

- 첫 번째 인자인 reducer 함수가 반환하는 값으로 state 를 갱신하는 역할

const [state, dispatch] = useReducer(reducer, initialState, init);
state : 컴포넌트에서 사용할 상태
initialState : 초기 state
init : 초기 함수

 

dispatch

- state 업데이트를 위한 요구

- reducer 함수를 실행시키며, 컴포넌트 내에서 state 의 업데이트를 일으키기 위해서 사용하는 함수

- action 을 이용하여, 컴포넌트 내에서 state 의 업데이트를 일으키기 위해서 사용함

dispatch({ type: "decrement" })

 

action

- 요구의 내용

- 업데이트를 위한 정보를 가지고 있는 것

- Dispatch 의 인자가 되며, reducer 함수의 두 번째 인자인 action 에 할당됨

- 주로 type 이라는 값을 지닌 객체 형태로 사용됨

{ type: "decrement" }

 

reducer

- 컴포넌트 외부에서 state 를 업데이트 하는 로직을 담당하는 함수

- 현재의 state 와 action 을 인자로 받음

- action 의 값에 근거하여 기존의 state 를 대체할 새로운 state 를 반환한다

function reducer(state, action) {
  switch (action.type) {
    ...
}
기존의 state 를 변경하거나 , 추가하거나, 덮어쓰지 않는다

 

useReducer 예제

 

은행 예제

const ACTION_TYPES = {
  deposit : "deposit",
  withdraw : "withdraw"
}

const reducer = (state, action) => {
  switch(action.type) {
    case ACTION_TYPES.deposit : 
    return state + action.payload

    case ACTION_TYPES.withdraw :
      return state - action.payload

    default : 
      return state;
  }
}


function App() {
  const [number, setNumber] = useState(0);
  const [money, dispatch] = useReducer(reducer, 0)

  return (
    <div>
      <h2>useReducer 의 은행에 오신것을 환영합니다.</h2>
      <p>잔고 : {money}원</p>
      <input 
      type="number"
      value={number}
      onChange={(e) => setNumber(parseInt(e.target.value))}
      step="1000"
       />
       <button onClick={() => {
        dispatch({type : ACTION_TYPES.deposit , payload : number})
       }}
       >예금</button>
       <button onClick={() => {
        dispatch({type : ACTION_TYPES.withdraw, payload: number})
       }}>출금</button>
    </div>
  );
}

- money 라는 state 를 만들어주고, dispatch 를 사용해 그 state 를 어떻게 업데이트 할 것인지 정해준다.
- reducer 라는 함수를 만들어주고, 현재 state 와 action 을 인자로 받아준다.
- money 가 현재 state 가 된다.
- reducer 에서 type 을 switch 의 case를 통해 dispatch 에서 고른 type 별로 이벤트를 추가해주는 것이라고 생각하는게 편하다.
- action 의 type 이름은 ACTION_TYPES 와 같은 constant 를 따로 만들어주어서 관리하면 편리하다.

 

출석부 예제

 

App.js

const reducer = (state, action) => {
  switch(action.type){
    case 'add-student' :
      const name = action.payload.name;
      const newStudent = {
        id : Date.now(),
        name,
        isHere : false,
      }
      return { 
        count : state.count + 1,
        students : [...state.students, newStudent]
      }

      case 'delete-student' :
        return {
          count : state.count - 1,
          students : state.students.filter(
            (student) => student.id !== action.payload.id
          )
        }

      case 'mark-student' : 
        return {
          count: state.count,
          students : state.students.map((student) => {
            if(student.id === action.payload.id){
              return {...student, isHere : !student.isHere}
            }
            return student;
          })
        }
    
      default : 
      return state;
  }
}

const initialState = {
  count : 0,
  students : []
}


function App() {
  const [name, setName] = useState("");
  const [studentsInfo , dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      <h1>출석부</h1>
      <p>총 학생 수 : {studentsInfo.count}</p>
      <input
      type="text"
      placeholder='이름을 입력해주세요'
      value={name}
      onChange={(e) => setName(e.target.value)}
       />
      <button onClick={() => {
        dispatch({type: 'add-student', payload: {name}})
      }}>추가</button>
      {studentsInfo.students.map((student) => {
        return <Student key={student.id} name={student.name} dispatch={dispatch} id={student.id} isHere={student.isHere} />
      })}
    </div>
  );
}

- useReducer 를 호출할 때, studentsInfo 를 현재 상태 (state) 로 정해준다. 이 state 는 initialState 와 같다.

- initialState 에는 초기값을 넣어주도록 한다. ( 학생 수, 학생 정보 )

- action.type 에는 아래와 같이 3가지로 구성이 되어있다.

"add-student" : 학생의 정보를 추가하는 action
"delete-student" : 클릭한 학생의 id 와 데이터의 id 가 같은 것이 있을 경우 그 데이터를 삭제하는 action
"mark-student" : 학생 이름을 클릭했을 때 학생의 이름에 줄이 가게끔 하는 action (isHere 의 값을 변경시키는 action)

- 학생을 추가하거나 제거할 때는 count 의 값을 빼거나 더하게 해서, 학생의 수를 알게 해주었다.

 

Student.jsx

import React from "react";

function Student({ name, dispatch, id, isHere }) {
  return (
    <div>
      <span
        style={{
          textDecoration: isHere ? "line-through" : "none",
          color: isHere ? "gray" : "black",
        }}
        onClick={() => {
          dispatch({ type: "mark-student", payload: { id } });
        }}
      >
        {name}
      </span>
      <button
        onClick={() => {
          dispatch({ type: "delete-student", payload: { id } });
        }}
      >
        삭제
      </button>
    </div>
  );
}

export default Student;

- 학생의 이름을 클릭하면 dispatch 를 사용해, mark-student 이벤트를 실행할 수 있도록 했다. ( 이름에 줄이 가는 action )

- 스타일도 그에 맞게끔 삼항연산자를 사용해 변경되게 해주었다.

- 삭제 버튼을 클릭하면 dispatch 를 사용해, delete-student 이벤트를 실행할 수 있도록 했다. ( 학생 정보를 삭제하는 action )

- payload 는 모두 현재 학생의 id 를 알 수 있게 하기위해서 현재 학생의 id 를 값으로 넣어주었다.

'React > 이론공부' 카테고리의 다른 글

React.Fragment  (0) 2023.01.25
React.memo  (0) 2023.01.25
[React Hooks] useCallback  (0) 2023.01.25
[React Hooks] useMemo  (0) 2023.01.24
[React Hooks] useContext & Context  (0) 2023.01.24