[React] useReducer 을 사용하는 경우 기록하기

김현수·2024년 3월 6일
0

React

목록 보기
24/29


🖍️ useReducer 사용하는 경우

  • React의 상태 관리 훅 중 하나
  • 복잡한 상태 로직을 컴포넌트에서 분리
  • 다수의 하위 값이 포함된 상태를 다룰 때 유용

  • 특히 여러 상태가 서로 의존적일 때
  • 다음 상태가 이전 상태에 의해 계산되어야 할 때 권장

  • useState 보다 더 선언적인 방법으로 상태 업데이트 로직을 외부에 두고 싶을 때 유리

  • 구조

    • reducer

      • 애플리케이션의 상태를 변경하는 함수
      • 이전 상태와 액션을 인자로 받아 새 상태를 반환
    • dispatch

      • 액션을 발생시키는 함수
      • 이 함수를 통해 reducer에 액션을 전달
    • action

      • 상태 업데이트를 위한 정보를 담은 객체
      • 일반적으로 type 필드를 포함

  • 기능과 효과

    • 상태 로직의 분리와 재사용성

      • reducer 함수를 컴포넌트 외부에 정의함
      • 상태 업데이트 로직을 컴포넌트로부터 분리
      • 이는 코드의 가독성을 향상시키고 로직의 재사용성을 높임
    • 예측 가능한 상태 관리

      • 모든 상태 변화가 reducer 함수를 통해 발생
      • 상태 변화를 더 쉽게 추적하고 예측
    • 디버깅 용이

      • 상태 업데이트 로직이 한 곳에 집중

  • 카운터

import React, { useReducer } from 'react';

// Reducer 함수 정의
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};

const Counter = () => {
  // useReducer 훅 사용
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </div>
  );
};

export default Counter;

  • 투두 리스트

import React, { useReducer, useState } from 'react';

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.payload, completed: false }];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.payload);
    default:
      throw new Error();
  }
};

const TodoList = () => {
  const [text, setText] = useState('');
  const [todos, dispatch] = useReducer(todoReducer, []);

  const handleAddTodo = () => {
    dispatch({ type: 'ADD_TODO', payload: text });
    setText('');
  };

  return (
    <div>
      <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id} onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}>
            {todo.completed ? <s>{todo.text}</s> : todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
};

  • 사용자 인증 상태 관리

import React, { useReducer } from 'react';

const authReducer = (state, action) => {
  switch (action.type) {
    case 'LOGIN':
      return { ...state, isAuthenticated: true, user: action.payload };
    case 'LOGOUT':
      return { ...state, isAuthenticated: false, user: null };
    case 'UPDATE_PROFILE':
      return { ...state, user: { ...state.user, ...action.payload } };
    default:
      throw new Error();
  }
};

const initialAuthState = {
  isAuthenticated: false,
  user: null
};

const AuthComponent = () => {
  const [state, dispatch] = useReducer(authReducer, initialAuthState);

  const handleLogin = () => {
    dispatch({ type: 'LOGIN', payload: { username: 'user1' } });
  };

  const handleLogout = () => {
    dispatch({ type: 'LOGOUT' });
  };

  return (
    <div>
      {state.isAuthenticated ? (
        <div>
          <p>Welcome, {state.user.username}!</p>
          <button onClick={handleLogout}>Logout</button>
        </div>
      ) : (
        <button onClick={handleLogin}>Login</button>
      )}
    </div>
  );
};

  • 테마 모드 전환

import React, { useReducer } from 'react';

const themeReducer = (state, action) => {
  switch (action.type) {
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'dark' ? 'light' : 'dark' };
    default:
      throw new Error();
  }
};

const ThemeSwitcher = () => {
  const [themeState, dispatch] = useReducer(themeReducer, { theme: 'light' });

  const toggleTheme = () => {
    dispatch({ type: 'TOGGLE_THEME' });
  };

  return (
    <div style={{ background: themeState.theme === 'dark' ? 'black' : 'white', color: themeState.theme === 'dark' ? 'white' : 'black' }}>
      <p>Current theme is {themeState.theme}</p>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
};

  • 페이지네이션

import React, { useReducer } from 'react';

const paginationReducer = (state, action) => {
  switch (action.type) {
    case 'SET_CURRENT_PAGE':
      return { ...state, currentPage: action.payload };
    case 'SET_TOTAL_COUNT':
      return { ...state, totalCount: action.payload };
    case 'SET_PAGE_SIZE':
      return { ...state, pageSize: action.payload };
    default:
      throw new Error();
  }
};

const PaginationComponent = () => {
  const [paginationState, dispatch] = useReducer(paginationReducer, { currentPage: 1, pageSize: 10, totalCount: 100 });

  const goToPage = (pageNumber) => {
    dispatch({ type: 'SET_CURRENT_PAGE', payload: pageNumber });
  };

  return (
    <div>
      <p>Current Page: {paginationState.currentPage}</p>
      <button onClick={() => goToPage(paginationState.currentPage - 1)}>Previous</button>
      <button onClick={() => goToPage(paginationState.currentPage + 1)}>Next</button>
    </div>
  );
};

  • 데이터 로딩 및 에러 처리

import React, { useReducer, useEffect } from 'react';

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return { ...state, isLoading: true, isError: false };
    case 'FETCH_SUCCESS':
      return { ...state, isLoading: false, data: action.payload };
    case 'FETCH_FAILURE':
      return { ...state, isLoading: false, isError: true };
    default:
      throw new Error();
  }
};

const DataFetchingComponent = ({ url }) => {
  const [fetchState, dispatch] = useReducer(dataFetchReducer, { isLoading: true, data: null, isError: false });

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });
      try {
        const response = await fetch(url);
        const result = await response.json();
        dispatch({ type: 'FETCH_SUCCESS', payload: result });
      } catch (error) {
        dispatch({ type: 'FETCH_FAILURE' });
      }
    };

    fetchData();
  }, [url]);

  return (
    <div>
      {fetchState.isError && <p>Error fetching data.</p>}
      {fetchState.isLoading ? <p>Loading...</p> : <pre>{JSON.stringify(fetchState.data, null, 2)}</pre>}
    </div>
  );
};
profile
일단 한다

0개의 댓글