Redux 사용 이유와 구조
포스트
취소

Redux 사용 이유와 구조

본 포스트는 사용 이유를 제외하고 순수한 Redux에 대한 정리글입니다. ( React-Redux X )

🫗 React에서 Redux를 사용하는 이유

React는 단방향 데이터 바인딩 원칙입니다.
항상 부모 컴포넌트에서 자식 컴포넌트로 props를 이용해서 데이터를 전달합니다.

소규모 프로젝트라면 단방향 데이터 바인딩으로 인한 불편함은 거의 느끼지 못합니다.
따라서 Redux도 굳이 사용할 필요가 없습니다.

하지만 규모가 커질수록 데이터가 많아지고 컴포넌트간의 관계가 복잡해집니다.
이런 상황에서는 많은 컴포넌트들이 공유하는 데이터가 존재하기 마련입니다.
예를 들면 로그인한 유저, theme 등의 데이터들이 존재합니다.

하지만 React에서 컴포넌트끼리 데이터를 공유하려면 공통의 상위 컴포넌트에서 정의하고 props를 이용해서 전달해야합니다.
하지만 많은 컴포넌트가 공유하면 그만큼 많은 props를 작성해야합니다.
이 부분은 가독성, 유지·보수에도 안좋고 개발자 입장에서도 일일이 작성하기 귀찮아집니다.
( 귀찮다는 표현이 안좋게 들릴 수 있지만, 실제로 정말 귀찮고 헷갈립니다… )

react-state

위 이미지 같은 구조를 갖는다면 2, 3이 데이터를 공유해야 하기 때문에 사용하지 않는 1에게 데이터를 선언하고 props로 내려줘야 하고, 4도 사용하진 않지만 8에게 데이터를 전달하기 위해서 props로 전달 받아야 합니다.

🗃️ Redux란

상태 관리를 위한 도구입니다.

큰 객체에 공용으로 사용할 데이터를 넣어두고 필요한 컴포넌트에서 가져와서 사용하는 구조라고 생각하면 됩니다.
만약 큰 객체에 데이터가 변경된다면 그 데이터를 참조하는 컴포넌트에서도 리랜더링이 발생합니다.
따라서 컴포넌트들이 같은 데이터를 공유할 수 있게 됩니다.

📑 Redux의 핵심 개념들

React에서는 Redux-Toolkit을 사용하는 것이 좋기 때문에 일반 TypeScript에서 사용하는 예시를 작성하겠습니다.
여기서 코드의 흐름을 이해하면 RTX를 공부할 때 도움이 됩니다.

0️⃣ store

저장소 역할을 하는 큰 객체입니다.

1
2
3
import { createStore } from 'redux'

const store = createStore(/* reducer */);

1️⃣ action

store의 내부 상태를 변경하는 유일한 방법입니다.
typepayload를 갖는 객체입니다.

  • type: 어떤 상태를 변경하는지에 사용
  • payload: 상태를 어떻게 변경하는지에 사용
1
2
3
4
5
6
7
8
9
10
11
// 액션
const fetchUserAction = {
  type: "fetch/user",
  payload: 1,
};

// 액션 크리에이터 ( 액션을 생성해주는 함수 )
const fetchUser () => ({
  type: "fetch/user",
  payload: 1,
});

2️⃣ dispatch

상태 변경을 요청하는 메서드입니다.
action을 인수로 넣어줍니다.

1
2
3
4
5
// 액션 사용
store.dispatch(fetchUserAction);

// 액션 크리에이터 사용
store.dispatch(fetchUser());

3️⃣ reducer

action에 의해서 어떻게 상태가 변경될지에 대한 내용을 작성하는 순수 함수입니다.
내부 구조는 마음대로 작성하되 반드시 새로운 상태 객체를 반환해야 합니다. ( 불변성 )
기존 state(상태)와 전달받은 action을 통해서 새로운 state(상태)를 반환하는 순수 함수입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// "state"는 기존 상태
// "action"은 "dispatch()"로 전달받은 "action"
// 자동으로 값이 들어옵니다. ( "addEventListener(type, callback)"에서 "callback(event)"의 매개변수로 "event" 객체가 들어오는 것처럼 내부적으로 처리 )
const userReducer = (state, action) => {
  switch(action.type) {
    case "fetch/user":
      // 여기서 상태를 변경할 때 spread operator를 이용해 새로운 객체 반환
      return {
        ...state,
        user: action.payload,
      };

      // 아래와 같은 형식을 사용하면 안됨 ( 단, "immer" 사용 시 문제 없음 )
      // state.user = action.payload;
      // return state;

      default:
        return state;
  }
}

4️⃣ 전체 코드와 흐름 정리

store는 상태를 저장하는 하나의 큰 객체입니다.
dispatch()를 통한 action 전달으로만 store를 변경할 수 있습니다.
전달된 actionreducer를 통해서 새로운 state를 만들어냅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { createStore } from 'redux'

// 액션 크리에이터 ( 액션을 생성해주는 함수 )
const fetchUser () => ({
  type: "fetch/user",
  payload: { name: "john", age: 25 },
});

// 리듀서
const userReducer = (state, action) => {
  switch(action.type) {
    case "fetch/user":
      return {
        ...state,
        user: action.payload,
      };

    default:
      return state;
  }
}

// 스토어
const store = createStore(userReducer);

// 디스패치
store.dispatch(fetchUser());

// 내부 상태 확인
store.getState();

이후에 읽어볼만한 포스트 ( Redux-Toolkit )

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.