Studying/React

리액트 공부하기 - 리덕스

creamymood 2025. 4. 28. 15:31

진짜.. 리덕스 공부하다가.. 개발자.. 못하겠는데 .. 처음 벽을 느꼈다..... ㅠ

(물론 다 어렵지만, 내 기준.. 너모 어려운 상태관리)

 

할건 너무 많은데.. 진전이 안나가서 더 조급하고.... 힘든 상태 ....... 상태.. (상태 단어 싫어..)

하지만.... 또 익숙해지겠지요 ... ?


1. 리덕스 설명 및 기본 문법

2. 리덕스 데이터 흐름 & 구성 요소 자세한 설명

3. 리덕스 쉽게 이해하기

4. 리덕스 쉬운 예제

 


1. 리덕스 설명 및 기본 문법

 

리덕스란?

 

리액트에서 **리덕스(Redux)**는 상태 관리 라이브러리로, 애플리케이션의 상태(state)를 효율적으로 관리하기 위해 사용됩니다.

리액트 애플리케이션에서 여러 컴포넌트들이 서로 상태를 공유하고,

그 상태를 업데이트하거나 사용하는 과정이 복잡해지면 리덕스가 큰 도움이 됩니다.

리덕스의 기본 개념

리덕스는 단일 상태 트리(single state tree)와 불변성(immutability)을 기반으로 합니다.

이 말은 애플리케이션의 전체 상태가 하나의 객체로 관리되고, 상태를 직접 수정하는 것이 아니라 **액션(action)**을 통해 상태를 변경하는 방식입니다.

리덕스의 주요 개념은 다음과 같습니다

  1. State: 애플리케이션의 상태를 나타내는 객체입니다. 리덕스에서는 애플리케이션의 전체 상태를 하나의 객체로 관리합니다.
  2. Action: 상태를 변화시키기 위해 발생하는 이벤트입니다. 액션은 type 속성을 가지며, 추가적인 데이터를 payload로 가질 수 있습니다.
  3. Reducer: 액션을 처리하여 상태를 업데이트하는 함수입니다. 리듀서는 이전 상태와 액션을 받아 새로운 상태를 반환합니다.
  4. Store: 애플리케이션의 상태를 저장하고 관리하는 객체입니다. 리덕스에서는 하나의 Store를 사용하여 상태를 관리합니다.
  5. Dispatch: 액션을 Store로 보내 상태를 변경하는 메서드입니다.
더보기

쉽게 이해하기 ✨

state : 데이터

store : 데이터 저장소

액션 : ~이렇게 바뀌었어. 또는 바뀔거야. 바꿔줘. 하는 "지시서" <객체 형태>

액셩 생성자 : 액션을 만드는 함수. "지시서 작성기" <객체 만드는 함수>

dispatch : 액션을 리듀서로 보내는 행동. 함수 "이렇게 바뀔거야. 실행해줘 ~ "

리듀서 = 실제로 바꾸는 것.

카페를 생각해본다면 ? 

  • 손님이 "아메리카노 한 잔 주세요!" (액션) → 디스패치 (요청 버튼 누른 거야)
  • 카페 직원이 "아메리카노 주문 들어왔네, 메뉴얼 보니까 에스프레소 + 물 추가네" → 리듀서 (메뉴얼대로 처리한 거야)
  • 그래서 아메리카노가 완성돼서 나오는 거지!

리덕스 기본 문법

리덕스를 사용하기 위해서는 몇 가지 기본적인 코드가 필요합니다.

1. 액션 정의 (Action)

액션은 상태를 변경하는 의도를 나타내는 객체

// action.js
export const ADD_TODO = 'ADD_TODO';

export const addTodo = (text) => ({
  type: ADD_TODO,
  payload: text,
});

2. 리듀서 정의 (Reducer)

리듀서는 액션을 받아서 새로운 상태를 반환하는 함수입니다. 리덕스에서는 상태를 직접 수정하지 않고, 항상 새로운 객체를 반환해야 합니다.

// reducer.js
import { ADD_TODO } from './action';

const initialState = {
  todos: [],
};

const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state, // 기존 상태 복사
        todos: [...state.todos, action.payload], // 새로운 할 일 추가
      };
    default:
      return state;
  }
};

export default todoReducer;

3. Store 생성

리덕스에서 상태를 관리하는 Store를 생성합니다.

// store.js
import { createStore } from 'redux';
import todoReducer from './reducer';

const store = createStore(todoReducer);

export default store;

4. 리액트와 리덕스 연결 (React + Redux)

리액트 컴포넌트에서 리덕스를 사용하려면 react-redux 라이브러리의 Provider와 useSelector, useDispatch 훅을 사용해야 합니다.

npm install react-redux

리덕스를 연결할 때 Provider로 앱을 감싸고, 컴포넌트에서 상태를 가져오거나 액션을 디스패치할 수 있습니다.

// App.js
import React from 'react';
import { Provider, useSelector, useDispatch } from 'react-redux';
import store from './store';
import { addTodo } from './action';

const TodoApp = () => {
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  const handleAddTodo = (text) => {
    dispatch(addTodo(text)); // 액션 디스패치
  };

  return (
    <div>
      <h1>Todos</h1>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
      <button onClick={() => handleAddTodo('New Todo')}>Add Todo</button>
    </div>
  );
};

const App = () => (
  <Provider store={store}>
    <TodoApp />
  </Provider>
);

export default App;

리덕스 사용 흐름 정리

  1. 액션: 사용자가 무엇을 하기를 원하는지 정의합니다. (예: ADD_TODO)
  2. 리듀서: 액션을 처리하여 상태를 업데이트합니다.
  3. 스토어: 전체 상태를 관리하고, 액션을 디스패치하면 새로운 상태가 반환됩니다.
  4. 컴포넌트: useSelector로 상태를 가져오고, useDispatch로 액션을 디스패치합니다.

쉽게 이해해보기

더보기

 

이름 역할 쉬운 비유
store 상태를 저장하는 창고 "상태의 보관함"
state 저장된 값 숫자, 글자 등 실제 데이터
action 상태를 바꾸고 싶다는 요청 "나 증가하고 싶어요!" 라는 말
reducer 요청을 받고 상태를 바꾸는 함수 "좋아, 숫자 1 더해줄게!"
dispatch 요청(action)을 보내는 것 action을 reducer에 전달하는 택배

 

쉬운 흐름으로 보기 (Counter 예제 기준)

1. 📦 store 만들기

  • 상태를 보관할 공간을 만든다.

2. ✋ action 만들기

  • 상태를 바꾸기 위한 요청을 만든다. (예: 증가하기)

3. 🛠 reducer 만들기

  • 요청을 받아서 상태를 바꾸는 함수.

4. 🎯 dispatch로 요청하기

  • 버튼을 눌렀을 때, "나 숫자 증가시켜줘!" 하고 요청 보냄.

5. 👀 useSelector로 상태 가져오기

  • 현재 숫자 값을 화면에 보여주기.

🔁 전체 흐름 요약

[버튼 클릭]
   ↓
dispatch(action)
   ↓
reducer(action을 보고 상태 바꿈)
   ↓
store에 저장
   ↓
useSelector로 가져옴
   ↓
화면 업데이트

 


1. 리덕스 데이터 흐름 및 구성 요소 자세한 설명

리덕스 데이터 흐름

 

ui 상태변화가 필요한 이벤트가 발생하면

상태를 어떻게 변화시킬 정보를 담은 액션이라는 객체 만들어지고

이 객체는 디스패치라는 함수를 통해서 

리듀서라는 함수로 전달이 되서

이 리듀서에서 액션 객체를 해석하고 그 값에 따라서 스토어에 들어있는 전역상태 변경

상태가 변경되면 ui가 변경된 상태에 맞춰서 리렌더링.

 

 

플러스 버튼 누르면 카운터에 값을 더할것이다. 라는 의미를 담은 액션 객체 생성

 

이렇게 만들어진 객체는, 디스패치 함수로 전달

리듀서라는 함수로 전달

그러면 리듀서는 액션 타입에 있는 increment라는 것을 해석하고

아~ 카운터 값을 올리겠다는 뜻이구나. 라고 해석하고 상태 변경

 

UI도 상태에 맞춰 변경되었습니다 !


리덕스 구성 요소

 

1. action:

액션은 상태를 어떻게 변화시킬지에 대한 내용이 담긴 객체!

payload라고 해서, 특정한 값을 활용해서 상태를 변경시키겠다는 내용을 담은 추가로 넘겨주기도

 

 

그리고 객체를 매번 만드는게 귀찮다면

함수를 그 때 그 때 만들어서 액션객체를 반환시키는 함수를 만들어서 

함수에 전달 받은 인자를 통해서 payload값을 동적으로  좀 더 편리하게 액션 객체를 만들 수 있다.

 

 

더보기

 

① 간단한 액션 & 액션 생성자 예시 1

// 1. 액션 (요청서)
// 어떤 일이 일어났다는 것을 알려주는 객체야
const addItemAction = {
  type: 'ADD_ITEM', // 이 액션이 무슨 의미인지 나타내는 필수 속성
  payload: { name: 'Apple', quantity: 3 } // (선택) 추가로 필요한 데이터
};

// 2. 액션 생성자 (요청서 작성기)
// 액션 객체를 만들어주는 함수야
function createAddItem(name, quantity) {
  return {
    type: 'ADD_ITEM',
    payload: { name, quantity }
  };
}

// 사용 예시
const action = createAddItem('Banana', 5);
// => { type: 'ADD_ITEM', payload: { name: 'Banana', quantity: 5 } }

요약:

  • 액션은 그냥 "이거 추가해줘!" 라고 적은 요청서.
  • 액션 생성자는 매번 손으로 작성하기 귀찮으니까 자동으로 요청서를 만들어주는 함수!

② 간단한 액션 & 액션 생성자 예시 2

// 1. 액션 (요청서)
// 단순히 상태를 바꾸고 싶을 때 사용하는 객체
const toggleLightAction = {
  type: 'TOGGLE_LIGHT' // 이 액션은 "불을 켜거나 꺼줘!" 라는 의미야
};

// 2. 액션 생성자 (요청서 작성기)
// 이 액션을 쉽게 만들어주는 함수
function toggleLight() {
  return {
    type: 'TOGGLE_LIGHT'
  };
}

// 사용 예시
const action = toggleLight();
// => { type: 'TOGGLE_LIGHT' }

요약:

  • 액션은 "불 켜줘!" 같은 짧은 지시문.
  • 액션 생성자는 이 지시문을 만들어주는 버튼 같은 거야!

 

 

2. dispatch 함수

 

Dispatch는 store의 내장 함수 중 하나로, action을 발생시킵니다. action을 파라미터로 전달하고 reducer를 호출합니다.
dispatch(addTodo({ id: id, text: todo, done: false }));

 

2-1. 액션 객체의 두가지 타입 중 한가지인

객체를 바로 작성한 경우, 작성된 객체를 dispatch 함수의 인자로 전달 

 

 

2-2. 액션 생성자로 

함수를 호출해서 액션 객체가 리턴이 되면, 

리턴값을 디스패치 함수에 인자로 전달하는 방식으로 액션객체가 리듀서로 전달되게 된다.

 

3. reducer

리듀서는 두가지 인자를 갖는데,

첫번째 인자는 상태값, 두번째 인자는 dispatch로 부터 전달 받을 액션값

 

액션의 타입에 들어있는 문자열에 따라서 로직이 바뀌기 때문에, 스위치문을 통해서 액션값 검사한 뒤에.

 

더보기

💖리듀서(Reducer)란?

리듀서는 상태(state)액션(action) 을 받아서, 새로운 상태(new state) 를 반환하는 순수 함수입니다.

  • 현재 상태(state) + 발생한 액션(action) ➔ 변경된 새로운 상태(new state)
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

💖리듀서에서 "상태값"의 의미

리듀서 안에 있는 상태값(state)현재 관리하고 있는 데이터입니다.

  • 초기값은 리듀서가 처음 실행될 때 사용됩니다.
  • 상태값은 리듀서가 "관리하는" 대상입니다.
  • 리듀서는 "내가 맡은 상태를 어떻게 업데이트할지"만 신경 씁니다.

리듀서는 상태를 직접 생성하는 게 아니라, 변경 규칙을 정의하는 역할입니다.

const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  // state = 현재 상태값, action = 발생한 사건
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

이 코드에서는 initialState가 기본 상태입니다.
액션이 발생할 때마다, 리듀서가 이 상태를 어떻게 변경할지 규칙을 적용합니다.


💖 액션(action)의 의미

액션은 "무슨 일이 일어났는지"를 설명하는 객체입니다.

보통 형태는 이렇게 생겼어요:

{ type: 'INCREMENT' }
  • type: 필수. "어떤 종류의 사건"인지 알려줍니다.
  • payload: (선택) 추가 데이터. (예: 업데이트할 값 등)

💖 요약 정리

구분 역할
상태(state) 현재 저장하고 관리하는 데이터
초기값(initialState) 리듀서가 처음 사용할 기본 상태
액션(action) 어떤 일이 발생했는지 나타내는 객체
리듀서(reducer) 상태와 액션을 받아서 새로운 상태를 반환하는 순수 함수

👏🏻 최종 정리

  • 리듀서는 상태(state)를 관리하는 역할을 합니다.
  • 초기 상태(initialState) 는 따로 설정해 두고, 리듀서는 그걸 기준으로 액션(action) 에 따라 상태를 업데이트(update) 합니다.
  • 액션값은 "이제 상태를 어떻게 바꿀지"를 알려주는 역할을 해요.
  • 리듀서는 "현재 상태 + 들어온 액션"을 기반으로 "새로운 상태"를 반환하는 거죠!

팁: 리듀서 작성할 때 요령

  • 항상 순수 함수로 작성하세요: 입력(state, action)이 같으면 결과가 항상 같아야 합니다.
  • 기존 상태를 직접 수정하지 마세요: 항상 새로운 객체를 리턴합니다. (immutable)
  • 초기값을 꼭 설정해두세요: 특히 리듀서가 처음 호출될 때를 대비해야 합니다.

 

 

세세하게 나뉜 리듀서가 여러개라면, 하나로 묶어야 함.

 

컴바인리듀서스 함수로 호출하고

인자로는 객체를 전달하는데 이 객체 안에 우리가 묶어주고 싶은 리듀서들을 순서대로 작성해서 전달하면 됌

더보기

 

세세하게 쪼개진 리듀서를, 하나의 리듀서만 원하기 때문에, 하나로 묶어주는 것.

 

💘 "리듀서를 묶는다"는 의미

여러 개의 리듀서를 하나로 합치는 것 = combineReducers 사용

import { combineReducers } from 'redux';

const rootReducer = combineReducers({
  user: userReducer,
  posts: postsReducer,
  comments: commentsReducer,
});
  • userReducer, postsReducer, commentsReducer가 각자 관리하던 상태를
  • 하나의 루트 리듀서(root reducer)합친 것입니다.

💘 왜 묶어야 할까? (필요성)

1. 상태를 역할별로 분리 관리하기 위해

  • 앱이 커지면 관리할 상태(state)도 많아집니다.
  • 예를 들면: 사용자 정보, 게시글 목록, 댓글 목록 등.
  • 하나의 리듀서에 모든 로직을 몰아 넣으면, 코드가 길고 복잡해져요. 유지보수가 매우 힘들어집니다.

리듀서를 역할별로 쪼개면 코드가 훨씬 깔끔해지고 명확해집니다.


2. 각 상태를 독립적으로 업데이트하기 위해

  • user 관련 액션은 userReducer만 신경 씁니다.
  • posts 관련 액션은 postsReducer만 신경 씁니다.

불필요한 상태 변경을 막고, 관리가 명확해집니다.


3. 확장성 확보

  • 새로운 기능이 추가될 때마다 새로운 리듀서를 추가하기만 하면 됩니다.
  • 기존 리듀서를 건드릴 필요가 없습니다.

큰 프로젝트에서도 확장성과 유연성이 좋아집니다.


💘 combineReducers는 뭘 해줄까?

combineReducers는 단순히, 여러 리듀서를 합쳐서 하나의 거대한 리듀서를 만들어주는 함수입니다.

  • 각각의 리듀서에 상태 조각(state slice) 을 맡깁니다.
  • 내부적으로 액션을 받아서, 각각의 리듀서에 알아서 전달합니다.

🧚‍♀️ 구조 예시:

const rootReducer = combineReducers({
  user: userReducer,    // user 상태 관리
  posts: postsReducer,  // posts 상태 관리
  comments: commentsReducer // comments 상태 관리
});

상태 구조도 자연스럽게 이렇게 됩니다:

state = {
  user: { ... },
  posts: { ... },
  comments: { ... }
}

✓ 핵심 정리

구분 내용
리듀서를 묶는 이유 상태를 역할별로 나누고, 코드 관리와 유지보수를 쉽게 하기 위해
combineReducers 역할 여러 리듀서를 하나로 합쳐 하나의 루트 리듀서를 만드는 것
장점 역할 분리, 확장성, 독립적인 상태 관리 가능

팁: 리듀서를 잘 나누는 방법

  • 도메인(주제) 별로 나누세요: (ex: user, posts, comments)
  • 기능(feature) 별로 나누는 것도 좋은 방법입니다.
  • 너무 잘게 쪼개면 오히려 관리가 복잡해질 수 있으니, 적절한 범위로 나누세요.

 

 

>>>>


"묶는다"라는 표현보다, 사실 쪼개고 나눈 다음, 다시 체계적으로 구성하는 것이 본질에 훨씬 가깝습니다.


정확한 이해: 쪼개기 ➔ 체계적 구성

1. 먼저 쪼갠다 (분리)

  • 큰 상태(State)작은 단위(Slice)나누고,
  • 각 단위를 담당하는 리듀서를 별도로 만든다.

➡️ 이 단계가 먼저입니다.

예를 들어:

나눌 대상 담당 리듀서
사용자 데이터(user) userReducer
게시글 데이터(posts) postsReducer
댓글 데이터(comments) commentsReducer

2. 그 다음 체계적으로 구성한다 (합침)

  • 각각 독립적으로 만든 리듀서들을,
  • combineReducers를 써서 조직화합니다.

➡️ 이것은 "묶기"보다는 "구성하거나 집합화"하는 과정에 가깝습니다.

const rootReducer = combineReducers({
  user: userReducer,
  posts: postsReducer,
  comments: commentsReducer,
});

결과적으로 전체 상태 구조가 잘 정리됩니다:

state = {
  user: { ... },
  posts: { ... },
  comments: { ... }
}

요약 정리

구분 설명
쪼개기 상태와 리듀서를 역할별로 나눈다
구성하기(조합하기) 나눈 리듀서들을 다시 모아 하나의 루트 리듀서로 구성한다

✓ 즉, "쪼개는 것"이 핵심이고, 묶는 것은 그 쪼개진 것들을 조직적으로 관리하는 과정입니다.
✓ 그래서 "리듀서를 나눈다" 라는 표현이 더 개념적으로 정확합니다!


팁: 리듀서 분리할 때 주의할 점

  • 리듀서는 "하나의 역할"에 집중해야 합니다. (Single Responsibility Principle)
  • 한 리듀서에 여러 주제를 섞지 마세요.
  • 상태 트리 구조(state tree)를 먼저 머릿속에 그려보면 분리가 훨씬 쉬워집니다.

 


👩🏻‍💻 왜 쪼개진 리듀서를 다시 "합치는가?"

1. Redux는 "하나의 리듀서"를 요구한다

Redux는 스토어(store)를 만들 때 단 하나의 리듀서 함수를 요구합니다.

const store = createStore(reducer);
  • 이 reducer는 "전체 앱 상태"를 관리할 수 있어야 합니다.
  • 그래서 여러 조각(user, posts, comments)을 나눴더라도
  • Redux 입장에서는 "하나"의 리듀서로 보여야 합니다.

✓ 즉, 시스템 규칙 때문에 결국 "합쳐진 하나"가 필요합니다!


2. 전체 상태 트리(state tree)를 구성하려면 필요하다

앱의 상태는 트리 구조입니다.
각 리듀서는 이 트리의 "한 가지 가지(branch)"를 관리합니다.

  • user 상태
  • posts 상태
  • comments 상태

각자 조각조각 있지만,
전체로 보면 "하나의 트리" 형태여야 합니다.

합친 결과: (예시)

state = {
  user: { id: 1, name: "홍길동" },
  posts: [ {id: 1, title: "첫번째 글"}, {id:2, title: "두번째 글"} ],
  comments: [ {id: 1, text: "좋아요!"} ]
}

✓ 이처럼 하나의 통합된 state 구조를 가지기 위해 합치는 겁니다.


3. Redux가 액션을 디스패치(dispatch)할 때 모든 리듀서에 알려야 한다

액션이 발생하면, 전체 상태를 업데이트할 준비가 되어 있어야 합니다.

  • 예를 들어, '로그인 성공' 액션이 발생하면
  • user 리듀서는 user 정보를 업데이트해야 하고,
  • posts나 comments 리듀서는 아무 것도 안 할 수도 있습니다.

combineReducers는 이 분배(dispatching)를 자동으로 처리해줍니다.

✓ 각각의 리듀서가 알아서 자기 맡은 상태만 업데이트하게 해줍니다.


✎ 요약 핵심 정리

구분 이유
Redux 규칙 스토어를 만들 때 리듀서는 무조건 "하나"여야 함
상태 트리 구성 상태를 조각조각 관리하지만, 전체는 하나의 트리로 보여야 함
액션 분배(dispatch) 모든 리듀서에 액션을 뿌려야 하고, 각 리듀서가 자기 파트만 업데이트해야 함

💖 최종 정리

쪼개는 것은 개발자 입장에서 관리 편의성을 위해 하는 것이고,
합치는 것은 Redux 시스템 입장에서 동작 요건을 충족시키기 위해 반드시 필요한 것입니다.

✔️ "개발자 마음대로 여러 리듀서를 쓸 수 없다"는 것, 이게 포인트입니다.


팁: 리듀서를 합칠 때 좋은 습관

  • 합칠 때 꼭 각 리듀서가 독립적으로 자기 상태만 다루는지 확인하세요.
  • combineReducers를 쓰면 상태 구조가 자동으로 딱 맞춰지니, 상태 구조 이름(user, posts 등)을 명확하게 설정하세요.

 

 

4. store 

 

 

createStore함수에 리듀서를 전달해서 스토어를 생성할 수 있는데,

이 때 리듀서를 하나만 전달받을 수 있기 때문에, 여러가지 리듀서는 하나로 묶어서 하나의 리듀서로 만들어야 한다.


구성 요소들을 다 만들었다면,

 


 

리덕스에서 비동기 처리를 하는 미들웨어, Thunk

 

리덕스(Redux)에서 "비동기 처리"란, 상태(state) 업데이트가 즉시 일어나지 않고, 어떤 작업이나 요청이 완료된 후에 상태가 변경되는 것을 의미해요. 보통 리덕스는 상태를 동기적으로 관리하는데, 예를 들어, 액션을 디스패치(dispatch)하면 즉시 리듀서에서 상태를 변경하게 되죠. 그런데 서버와 통신하거나, 데이터베이스에서 정보를 가져오는 등의 작업은 시간이 걸리기 때문에 비동기 처리가 필요합니다.

리덕스 자체로는 비동기 처리를 지원하지 않지만, 리덕스 미들웨어인 redux-thunk나 redux-saga 등을 사용해서 비동기 작업을 관리할 수 있습니다.

예시:

  1. redux-thunk를 사용할 경우:
    • 액션 생성자는 기본적으로 함수가 아닌 객체여야 하지만, redux-thunk 미들웨어를 사용하면 액션 생성자가 함수를 반환할 수 있게 되어, 비동기 처리가 가능해집니다.
  2. 서버에서 데이터를 가져온 뒤, 그 데이터를 상태에 반영하는 방식으로 비동기 작업을 할 수 있습니다.
// 예시: redux-thunk로 비동기 액션 처리
const fetchData = () => {
  return (dispatch) => {
    dispatch({ type: 'FETCH_START' });
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => {
        dispatch({ type: 'FETCH_SUCCESS', payload: data });
      })
      .catch(error => {
        dispatch({ type: 'FETCH_ERROR', error });
      });
  };
};

이렇게 하면 서버에서 데이터를 가져오는 동안 다른 작업을 할 수 있고, 데이터가 준비되면 상태를 업데이트할 수 있게 됩니다. 비동기 처리는 주로 데이터 로딩, API 호출, 타이머 등의 작업에서 사용됩니다.

 

다음 글에서 더 자세히 다뤄보겠다.


3. 리덕스 쉽게 이해해보기

 

1. 리듀서 (상태를 업데이트하는 요리사)

리덕스에서 리듀서는 **"요리사"**와 비슷한 역할을 합니다. 주방에서 요리사가 어떤 재료를 받아서 요리를 만드는 것처럼, 리듀서도 애플리케이션의 **상태(state)**를 관리하고 업데이트하는 역할을 합니다.

  • **상태(state)**는 "요리 재료"처럼 생각할 수 있어요. 요리사는 재료들을 섞거나 바꿔서 새로운 요리를 만들죠. 리듀서는 애플리케이션의 상태를 바꾸는 역할을 합니다.
  • 예를 들어, 리듀서가 "카운터" 상태를 관리한다면, 리듀서는 카운트를 업데이트하는 "요리사"입니다.

2. 액션 (요리사의 지시서)

**액션(action)**은 요리사에게 주는 **"지시서"**와 비슷합니다. 이 지시서에는 요리사가 무엇을 해야 할지에 대한 정보가 들어 있어요. 액션은 **어떤 일이 일어날지를 설명하는 "지시서"**입니다.

  • 예를 들어, "INCREMENT"라는 액션은 "카운터 숫자를 1 증가시키세요"라는 지시서입니다.

3. 스토어 (주방)

**스토어(store)**는 **"주방"**이에요. 주방에서는 요리 재료들이 있고, 요리사가 그 재료들로 요리를 만들고, 그 결과물을 서빙합니다. 스토어는 리덕스에서 모든 애플리케이션의 상태를 저장하는 장소입니다.

  • 주방에서는 여러 요리가 동시에 이루어질 수 있고, 각 요리는 각기 다른 재료와 조리법을 사용할 수 있습니다. 마찬가지로 스토어에서는 여러 리듀서들이 각기 다른 상태를 관리할 수 있어요.

4. 디스패치 (주방장이 주문을 내리다)

**디스패치(dispatch)**는 **"주방장이 요리사에게 지시를 내리는 과정"**이에요. 리액트에서는 사용자가 버튼을 클릭하거나 특정 행동을 할 때, 디스패치를 통해 액션을 보냅니다.

  • 예를 들어, "카운터 증가 버튼"을 클릭하면 디스패치가 "INCREMENT" 액션을 보내는 거예요. 이 액션은 요리사에게 카운트를 1 증가시키라는 지시를 내리는 것입니다.

5. 프로바이더 (주방의 정보 공유 시스템)

**프로바이더(Provider)**는 리덕스 상태를 리액트 컴포넌트들에 **"주방의 정보 시스템"**처럼 전달하는 역할을 합니다. 주방에서 요리사들이 각자 요리를 할 때 필요한 재료를 받아서 사용할 수 있도록 주방에서 정보를 제공하는 시스템과 비슷해요.

  • Provider는 리액트 애플리케이션의 루트 컴포넌트를 감싸서 모든 하위 컴포넌트들이 리덕스 스토어에 접근할 수 있도록 해줍니다.

리덕스 흐름 요약

  1. 액션(action): 사용자나 다른 이벤트에 의해 **"요리사의 지시서"**가 만들어짐.
  2. 디스패치(dispatch): 이 지시서를 **"주방장"**이 요리사에게 전달.
  3. 리듀서(reducer): 요리사는 지시서에 따라 **"상태(요리 재료)"**를 바꿈.
  4. 스토어(store): 주방(스토어)에서는 **"상태"**를 보관하고, 필요할 때 다른 요리사들이 참고할 수 있음.
  5. 프로바이더(Provider): 주방 정보 시스템을 통해 리액트 컴포넌트들이 **"상태"**에 접근 가능하게 만듦.

이렇게 리덕스는 주방에서 요리사가 요리를 만드는 과정처럼, 상태를 관리하고 업데이트하는 흐름을 가지고 있습니다. 각 단계가 서로 협력하면서 상태를 효율적으로 관리하고, 변화가 있을 때마다 앱에 반영되죠.

이제 리덕스의 흐름이 좀 더 이해가 되시나요?


4. 리덕스 예제

 

1. "액션 만들기"는 고정 문법일까?

거의 고정 문법처럼 쓰이긴 해!
하지만 진짜 딱 "고정"은 아니고,
"약속처럼" 사람들이 편하게 쓰려고 정해놓은 방식이야.

보통 액션을 이렇게 만들어:

const 액션이름 = (보내고 싶은 내용) => ({
  type: '액션 타입 이름',
  payload: 보내고 싶은 내용,
});
  • type: 필수야! 액션이 뭔지 구분하는 이름이야.
  • payload: 선택이야! 추가로 보내고 싶은 데이터가 있을 때 넣어.

그러니까,
형식은 자유지만, type은 꼭 있어야 하고, payload는 상황에 따라 넣는 거야.



2. 또 다른 간단하고 쉬운 예제

아주 쉬운 예로,
숫자 올리고 내리는 카운터(counter) 만들기 해볼게.


1) 액션 만들기

// 액션 만들기
export const increase = () => ({
  type: 'INCREASE',
});

export const decrease = () => ({
  type: 'DECREASE',
});
  • increase는 숫자를 1 올리는 액션
  • decrease는 숫자를 1 내리는 액션

2) 리듀서 만들기

// 리듀서 만들기
const initialState = { count: 0 };

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREASE':
      return { count: state.count + 1 };
    case 'DECREASE':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

export default counterReducer;
  • 초기값은 { count: 0 }
  • INCREASE면 count를 1 올리고
  • DECREASE면 count를 1 내림
  •  

3) 스토어 만들기

import { createStore } from 'redux';
import counterReducer from './counterReducer';

const store = createStore(counterReducer);

export default store;

createStore은 우리가 만든 리듀서를 인자로 받는다!


4) 리액트 컴포넌트 연결하기

 

우리가 만든 액션, 리듀서를 연결!

 

import React from 'react';
import { Provider, useSelector, useDispatch } from 'react-redux';
import store from './store';
import { increase, decrease } from './actions';

const Counter = () => {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => dispatch(increase())}>+1</button>
      <button onClick={() => dispatch(decrease())}>-1</button>
    </div>
  );
};

const App = () => (
  <Provider store={store}>
    <Counter />
  </Provider>
);

export default App;

 

1. provider로 연결하기

프로바이더의 자식컴포넌트가 되야지, 사용가능하니까. 카운터를 프로바이더로 감싸기

프로바이더에 store도 연결 해두기

 

2. 연결되었으니까, 상태값 꺼내 쓰기

: useSelector로 사용 가능!

이 때 useSelector의 인자 ? 

만약에 스토어에 여러개 리듀서가 있다면, 어떤 상태를 꺼내쓸까 ?  하는 것을 위해 함수가 인자로 전달받는다.

 

하나의 리듀서 밖에 없었으니까, 상태는 한개뿐일거라 바로 상태값 썼다.

 

상태값을, 이렇게 연결해주었습니다 !

 

상태값 연결~ 초기 상태값을 설정해주면, 이렇게 뜬다.

 

 

이제, 플러스/마이너스 버튼을 눌렀을 때 상태가 변경되도록 해보자!

만들어둔 액션을 디스패치 함수로 받아오고

 

 

이 디스패치 함수를 실행하면서, 우리가 만든 액션 객체를 전달하면, 이 액션객체가 디스패치를 타고 리듀서에 전달 될 것.

이 것은 버튼을 클릭할 때 실행이 되야 하니까

 

 

 

 

그러면 여러개의 상태가 있을 때는 ?

이렇게 카운터 앱을 하나 더 만들었다

액션 및 리듀서 두번째것 생성

 

이렇게 하면 두개의 액션 두개의 리듀서가 만들어졌는데

크리에이트스토어는 한개의 리듀서만 받을 수 있기 때문에, 두 리듀서를 합쳐줘야함!

 

컴바인리듀서 함수로, 우리가 합치고 싶은 리듀서들을 객체로 담아주기

이렇게 묶어지면, 하나의 리듀서로 만들어질텐데

루트 리듀서로 저장해주기

 

이걸 크리에이트스토어에 전달해주면 이제 두개의 상태 사용 가능

 

이 때, 여러개의 리듀서를 묶어서 만들었을 때 상태는 어떻게 사용 ?

 

이렇게 리듀서 이름을 콕 찝어서. !


출처 : 유튜브, 오즈코딩스쿨 학습자료, 공식문서, 챗지피티