[#61] Redux란

2025. 4. 24. 13:56·LG 유플러스 유레카 SW/React

Redux

자바스크립트 앱에서 상태(state)를 예측 가능하게 관리할 수 있도록 도와주는 상태 관리 라이브러리

애플리케이션의 상태를 하나의 스토어(store)에서 관리하고, 상태 변경은 액션(action)이라는 이벤트를 통해서만 이루어짐

 

사용 이유

  • 여러 컴포넌트가 같은 데이터를 써야 할 때, 상태 공유가 복잡해짐
  • Props drilling(깊은 자식에게 props 전달)이 심해짐
  • 상태 흐름이 예측되지 않음 ➡️ 디버깅 어려움

 

핵심 원칙

  1. 단일 스토어(Single Source of Truth): 애플리케이션의 모든 상태는 하나의 스토어에 저장됨
  2. 상태는 읽기 전용(State is Read-Only): 상태를 직접 변경할 수 없고, 액션을 통해서만 변경할 수 있음
  3. 변경은 순수 함수로만(Changes are made with Pure Functions): 리듀서(Reducer)라는 순수 함수가 이전 상태와 액션을 받아 새로운 상태를 반환

 

핵심 구성 요소

Store 상태가 저장되는 전역 저장소
State Store에 저장된 현재 상태 값
Action 상태에 어떤 변화가 일어날지를 설명하는 객체
Reducer 액션에 따라 상태를 변화시키는 순수 함수
Dispatch 액션을 스토어에 보내는 함수
Subscribe 상태 변경을 감지하여 리스너를 실행

 

 

데이터 흐름

UI (사용자 인터렉션)
   ↓
dispatch(action)
   ↓
reducer(state, action)
   ↓
새로운 state 반환
   ↓
store 업데이트 → UI 리렌더링

 

 

Redux 사용법

설치

npm install @reduxjs/toolkit react-redux

🔧 @reduxjs/toolkit 설치 이유

  • Redux 공식에서 권장하는 도구 모음
  • Redux 사용 시 자주 겪는 보일러플레이트 코드(반복되는 코드)를 줄여줌
  • createSlice, configureStore 등 편리한 함수 제공
  • 불변성 유지도 자동 처리 (Immer 내장)

 

store 구성

  • configureStore을 통해 스토어 구성 간편화
import { configureStore } from '@reduxjs/toolkit'
import { counterSlice } from './counterSlice'

export default configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
})

 

createSlice

  • Redux state 조각(Slice)을 정의하는 함수
  • 액션 + 리듀서를 한 번에 작성 가능
import { createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 1,
    label: '카운트',
  },
  reducers: {
    increament: (state, action) => {
      state.count += action.payload || 1 // 불변성? → OK! Immer가 내부적으로 처리
    },
    decreament: state => {
      state.count -= 1
    },
    reset: state => {
      state.count = 0
    },
  },
})
export const { increament, decreament, reset } = counterSlice.actions
export default counterSlice.reducer

👉🏻 reducers

  • 상태가 어떻게 바뀔지 정의하는 함수들을 담고 있음
  • 각각이 자동으로 액션 생성자도 만들어줌

👉🏻 state

  • initialState의 현재 상태
  • 리듀서 함수의 첫 번째 매개변수로 전달됨
  • 상태를 어떻게 바꿀지 여기서 정의
initialState: {
  count: 1,
  label: '카운트',
}
decreament: (state) => {
  state.count -= 1
}

👉🏻 action

  • 리듀서 함수의 두 번째 매개변수로 전달됨
  • 주로 payload에 전달한 값이 담겨있음
increament: (state, action) => {
  state.count += action.payload || 1
}
dispatch(increament(3))

 

React와 연결

  • Provider로 store 주입
// main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router.jsx'
import './index.css'

// Redux
import { Provider } from 'react-redux'
import store from './store/store.js'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <Provider store={store}>
      <RouterProvider router={router} fallbackElement={<div>로딩중...</div>} />
    </Provider>
  </StrictMode>
)

 

컴포넌트에서 사용

  • useSelector로 store에 있는 상태를 읽어옴
  • state.[slice 이름] 또는 state.[slice 이름].[속성] 형태로 접근
    • slice 이름 = store에서 등록한 리듀서 키
import React from 'react'
import { useSelector } from 'react-redux'

const Counter = () => {
  const { count, label } = useSelector(state => state.counter)
  return (
    <p>
      {label}: {count}
    </p>
  )
}

export default Counter
  • store에 있는 함수를 실행할 때는 dispatch로 감싸서 보내야 함
import Counter from '@/components/Counter'
import { decreament, increament, reset } from '@/store/counterSlice'
import React from 'react'
import { useDispatch } from 'react-redux'

const BlogPage = () => {
  const dispatch = useDispatch()

  return (
    <main>
      <h2>BlogPage</h2>
      <Counter />
      <button onClick={() => dispatch(increament())}>증가하기</button>
      <button onClick={() => dispatch(increament(10))}>증가하기(10)</button>
      <button onClick={() => dispatch(decreament())}>감소하기</button>
      <button onClick={() => dispatch(reset())}>리셋하기</button>
    </main>
  )
}

export default BlogPage

 

 

Redux 비동기 처리

createAsyncThunk

Redux Toolkit에서 제공하는 함수로, 비동기 작업을 처리하는 액션 생성자를 만듦

  • 용도: Promise 기반 비동기 로직을 포함하는 Redux 액션을 쉽게 만들기 위한 함수
  • 동작: 내부적으로 3가지 액션 타입을 자동으로 생성
pending 요청 중
fulfilled 요청 성공
rejected 요청 실패
// todoSlice.js
export const fetchTodos = createAsyncThunk('todo/fetchTodos', async () => {
  const response = await getTodosData()
  return response
})

✅ 첫 번째 매개변수: 'todo/fetchTodos'

  • 액션 타입의 접두사를 정의
  • 일반적으로 '슬라이스 이름/액션 이름' 형태로 작성
  • 내부적으로 세 가지 액션 타입이 생성됨
    • todos/fetchTodos/pending: 요청 시작 시
    • todos/fetchTodos/fulfilled: 요청 성공 시
    • todos/fetchTodoss/rejected: 요청 실패 시

✅ 두 번째 매개변수: async(queryParams = '', { rejectWithValue }) => { ... }

  • 비동기 작업을 수행하는 함수
  • 함수 내부의 결과에 따라 pending, fulfilled, rejected
  • 첫 번째 인자 queryParams
    • 액션 디스패치 시 전달할 수 있는 매개변수
    • 기본값 ''이 설정되어 있어 인자없이 호출 가능
  • 두번째 인자 { rejectWithValue }
    • thunkAPI 객체의 구조 분해 할당
    • thunkAPI 속성
dispatch 스토어의 dispatch 함수
getState 현재 스토어 상태를 가져오는 함수
extra 미들웨어에 전달된 extra 인자
requestId 현재 요청의 고유 ID
signal 요청 취소에 사용할 수 있는 AbortController.signal 객체
rejectWithValue 에러 처리를 위한 유틸리티 함수
fulfillWithValue 성공 처리를 위한 유틸리티 함수

 

extraReducers

외부에서 정의된 액션(createAsyncThunk로 생성된 액션: fetchTodos)에 응답하는 리듀서를 정의

  • builder.addCase: 특정 액션 타입에 대한 리듀서를 추가
// todoSlice.js
export const todoSlice = createSlice({
  name: 'todo',
  initialState: {
    todos: [],
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed',
    error: null,
  },
  extraReducers: builder => {
    builder
      .addCase(fetchTodos.pending, state => {
        state.status = 'loading'
        state.error = null
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.status = 'succeeded'
        state.todos = action.payload
        state.error = null
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.status = 'failed'
        state.error = action.error.message
      })
  },
})
🔄 fetchTodos.pending 데이터 요청이 시작되었을 때 실행
✅ fetchTodos.fulfilled 데이터 요청이 성공했을 때 실행
❌ fetchTodos.rejected 데이터 요청이 실패했을 때 실행

 

컴포넌트에서 dispatch로 호출

// TodoList.jsx
import { fetchTodos } from '@/store/todoSlice'
import React, { useEffect } from 'react'
import ListGroup from 'react-bootstrap/ListGroup'
import { useDispatch, useSelector } from 'react-redux'

const TodoList = () => {
  const dispatch = useDispatch()
  const { todos, status, error } = useSelector(state => state.todo)

  useEffect(() => {
    dispatch(fetchTodos()) // dispatch로 fetchTodos(비동기 액션 생성 함수)을 호출해야 getTodosData()가 호출되어 실행됨
  }, [dispatch])

  if (status === 'loading') return <div>Loading...</div>
  if (status === 'failed') return <div>{error}</div>

  return todos.length === 0 ? (
    <div>텅~</div>
  ) : (
    <ListGroup>
      {todos.map(todo => (
        <ListGroup.Item key={todo.id} className="d-flex justify-content-between align-items-center">
          <p className="flex-grow-1">{todo.desc}</p>
          <p className="m-2" style={{ fontSize: '0.75em' }}>
            {todo.createdAt}
          </p>
          <i className="bi bi-trash"></i>
        </ListGroup.Item>
      ))}
    </ListGroup>
  )
}

export default TodoList

 

728x90
반응형

'LG 유플러스 유레카 SW > React' 카테고리의 다른 글

[#63] 날씨 오픈 API 연동  (1) 2025.04.28
[#62] 용돈 기입장 프로젝트 (feat. React)  (0) 2025.04.28
[#60] React - Hook + 성능 최적화 정리  (0) 2025.04.23
[#59] React 쇼핑몰 - Shop 페이지 상품 필터링/정렬 + 페이지네이션  (0) 2025.04.22
[#58] React 쇼핑몰 - 장바구니 수정/삭제 + 반응형 Shop 페이지  (0) 2025.04.21
'LG 유플러스 유레카 SW/React' 카테고리의 다른 글
  • [#63] 날씨 오픈 API 연동
  • [#62] 용돈 기입장 프로젝트 (feat. React)
  • [#60] React - Hook + 성능 최적화 정리
  • [#59] React 쇼핑몰 - Shop 페이지 상품 필터링/정렬 + 페이지네이션
nueos
nueos
  • nueos
    nueos 공부 기록
    nueos
  • 전체
    오늘
    어제
    • 분류 전체보기 (191)
      • 해커톤 (1)
      • 네이버 BoostCamp (6)
      • LG 유플러스 유레카 SW (3)
        • React (21)
        • TypeScript (2)
        • JavaScript (2)
        • HTML+CSS (5)
        • Spring (7)
        • Java (6)
        • SQL (2)
        • Algorithm (8)
        • CX (6)
        • Git (2)
        • 프로젝트 (2)
        • 스터디 (9)
        • 과제 (8)
        • 특강 (1)
      • React (3)
      • Next (0)
      • Javascript (2)
      • HTML (2)
      • CSS (9)
      • Algorithm (6)
      • Database (0)
      • OS (13)
      • C++ (24)
      • Python (1)
      • jQuery (1)
      • Django (1)
      • Git (1)
      • 개발 지식 (3)
      • 정보 보안 (22)
      • 포렌식 (1)
      • 암호 (2)
      • 기타 (4)
      • 패스트캠퍼스 FE 프로젝트십 (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Stack
    스택
    Queue
    기술로바꾸는세상
    힙
    제주해커톤
    디지털혁신
    제주지역혁신플랫폼지능형서비스사업단
    완전 탐색
    디지랩챌린지
    큐
    exhaustive search
    heap
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
nueos
[#61] Redux란
상단으로

티스토리툴바