React 12. useReducer

윤태현·2023년 7월 10일
0

REACT

목록 보기
13/19
post-thumbnail

useReducer

  • React에서 상태를 관리할 때 사용하는 Hook
  • 상태를 업데이트하는 로직을 분리하여 컴포넌트 외부에서 정의할 수 있게 해주는 함수
  • 복잡한 상태 로직을 관리하거나, 여러 값들이 같은 액션에 따라 변경되는 경우 사용
const [state, dispatch] = useReducer(reducer, initialState, init);

reducer : state를 업데이트 하는 역할
initialState : reducer의 초기 상태 값
init (선택) : 초기 상태 값을 만드는 함수, 초기 상태 값을 지연 생성할 수 있음
dispatch : 액션을 발생시키는 함수, 정의된 액션을 reducer에 전달하여 상태를 업데이트할 수 있음 dispatch({ type: 'ACTION_NAME', payload: value })

예시 코드

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

function Counter() {
  const [state, dispatch] = useReducer(reducer, {count: 0});

  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'INCREMENT'})}>+</button>
      <button onClick={() => dispatch({type: 'DECREMENT'})}>-</button>
    </>
  );
}
  • 'INCREMENT'와 'DECREMENT' 액션을 dispatch하여 count 값을 증가시키거나 감소시킵니다.

주의할 점

1. action type은 일관되게 관리

  • 일반적으로 대문자로 작성하고 문자열 상수로 관리하는 것이 좋음
const ACTION_TYPES = {
  	INCREMENT: 'INCREMENT',
  	DECREMENT: 'DECREMENT',
};

function reducer(state, action) {
	switch (action.type) {
  		case ACTION_TYPES.INCREMENT:
    	// ...
    }
}

2. 상태 변경 로직은 불변성을 유지

  • React의 상태는 불변성을 유지해야 함
  • 상태를 직접 수정하지 말고 새로운 상태 객체를 생성해서 반환해야 한다.

3. dispatch는 비동기적으로 동작

  • dispatch함수는 비동기적으로 동작
  • 호출한 직후에는 상태가 바로 업데이트 되지 않을 수 있어서 상태의 최신 값을 확인하려면 호출 후에 컴포넌트의 다음 렌더링에서 확인해야 함

4. useReducer는 글로벌 상태 관리에도 사용될 수 있지만, 복잡성을 더해줄 수 있음

  • 글로벌 상태 관리를 위해 많이 사용하지만, 많은 상태를 관리하거나 액션의 종류가 많은 경우 ReduxMobX와 같은 상태 관리 라이브러리를 사용하는 것이 더 효율적일 수 있음

응용

  • useReducer를 사용한 출석부 관리 시스템
  • 학생을 추가하거나 삭제할 수 있고 출석이 완료 된 학생 이름을 클릭 시 줄을 긋는다.
// Attendance.jsx

import React, { useReducer, useState } from 'react';
import Student from './Student';


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

const reducer = (state, action) => {
    switch (action.type) {
        case 'add':	// 학생 추가
            const user = {
                id: new Date().getTime(),
                name: action.payload,
                isComplete: false
            }
            return {
                count: state.count+1,
                students: [...state.students, user]
            };
        case 'complete':	// 출석 여부
            return {
                count: state.count,
                students: state.students.map(e => (e.id === action.payload ? {...e, isComplete: !e.isComplete} : e))
            }
        case 'delete':	// 삭제
            return {
                count: state.count-1,
                students: state.students.filter(e => e.id !== action.payload)
            }
        default:
            return state.students;
    }
}

const Attendance = () => {
    const [name, setName] = useState('');
    const [studentList, dispatch] = useReducer(reducer, initialState);

    const addItem = () => {
        dispatch({type: 'add', payload: name});
        setName('');
    }

    return (
        <div>
            <h1>출석부</h1>
            <p>총 학생 수 : {studentList.count}</p>
            <div>
                <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
                <button onClick={addItem}>추가</button>
            </div>
            <ul>
                {studentList.students.map(item => (
                    <Student key={item.id} name={item.name} id={item.id} isComplete={item.isComplete} dispatch={dispatch}/>
                ))}
            </ul>
        </div>
    );
};

export default Attendance;
// Student.jsx

import React from 'react';

const Student = ({name, id, isComplete, dispatch}) => {
    return (
        <li>
            <span onClick={() => dispatch({type: 'complete', payload: id})} style={{ textDecoration: isComplete ? "line-through" : "none" }}>{name}</span>
            <button onClick={() => dispatch({type: 'delete', payload: id})}>삭제</button>
        </li>
    );
};

export default Student;

참고
별코딩 - useReducer

0개의 댓글