[React] React 에서 Redux 써보기 with Redux Toolkit

김희정·2024년 2월 22일
0

React

목록 보기
5/6

💎 들어가며

리액트로 화면을 만들다보니 테마 설정, 스크롤 위치 등 전역적인 상태가 필요해져, 상태 관리 라이브러리인 Redux를 시작하게 되었습니다.

이번 포스팅에서는 상태 관리란 무엇인지, 상태 관리 라이브러리인 Redux의 개념, 원칙, Redux를 이용한 상태관리 프로세스를 소개해보고자 합니다.


1. 상태 관리

상태 관리 도구는 왜 필요한 것인가?

상태와 상태관리 도구의 필요성, 상태 관리 라이브러리를 소개합니다.


1.1 상태란?

React에서는 컴포넌트(Component) 안에서 여러 상태들이 관리되고 있습니다.
부모로 받은 상태인 Props, 컴포넌트가 독자적으로 가질 수 있는 상태인 State가 있습니다.

컴포넌트는 상태가 변경됨에 따라 화면을 렌더링해줍니다. 아래의 코드는 컴포넌트의 상태 관리 예시입니다.

import React, {useState} from 'react';

const Clock = () => {
    const [date, setDate] = useState(new Date());
    
    const refreshDate = () => {
        setDate(new Date());
    }
    
    return (
        <div>
            <h1>Hello World</h1>
            <h2>It's {date.toLocaleTimeString()}.</h2>
            <button onClick={refreshDate}>Refresh</button>
        </div>
    );
};

export default Clock;

1.2 컴포넌트간 정보 공유

일반적인 컴포넌트에서는 상태관리 도구가 따로 필요하지 않습니다. 상태관리 도구가 필요한 때에는 컴포넌트에서 사용되는 하나의 상태가 다른 컴포넌트에서도 사용될 때 입니다.


Props Drilling 문제

이 때, 상태를 주고 받는 가장 직관적이고 고전적인 방법은 props를 통해 상태를 내려주는 방법입니다.

그런데 자식이 많아지면 상태 관리가 매우 복잡해지고, 상태를 관리하는 상위 컴포넌트에서 계속 내려받아야 합니다. 이러한 문제를 Props Drilling 이라고 합니다.


전역 상태 관리

Props Drilling 문제를 해결하는 방법은 상태를 전역적으로 관리하는 것입니다.

전역 상태 관리는 Content API를 사용하거나 상태 관리 라이브러리(redux, recoil, mobx)를 이용하여 관리합니다.


1.3 상태관리 라이브러리

상태 관리 라이브러리는 여러 종류가 있으며, 각 라이브러리에는 장단점이 존재하기에 목적에 맞게 사용하는 것이 중요할 것 같습니다.

NPM Trend 사이트에서 라이브러리 순위를 볼 수 있습니다.


2. Redux

리덕스(Redux)는 가장 인기 있는 JavaScript 상태관리 라이브러리입니다.


2.1 Principle 3

1) Single source of truth

"어플리케이션 상태는 모두 한곳에서 집중관리된다."

  • Store에서 집중 관리
  • 컴포넌트마다 동기화가 필요없이 store에서 각각에 연결되어 있는 컴포넌트에게 일괄적으로 처리

2) State is read-only

"상태는 불변하며, 오직 Action 만이 상태교체를 요청할 수 있다."

  • 상태는 불변데이터
  • 만약 상태를 교체해야된다면, 액션(Action)을 통해서 변경 가능

3) Changes are made with pure functions

"변화는 순수함수(Reducer)로 작성되어야 한다."

  • Action에 의해 상태가 어떻게 변화하는지 지정하기 위해 Reducer를 작성
  • Store(스토어) – Action(액션) – Reducer(리듀서)

2.2 Concepts

Redux의 핵심 구성 요소는 Store, Action, Reducer 입니다.

Store

스토어(Store)는 애플리케이션의 상태를 전역적으로 관리하는 하나의 공간입니다. 상태 정보가 필요할 때, 스토어에 접근하여 상태를 조회합니다.

useSelector() 메소드를 통해 스토어에 접근할 수 있습니다.


Action

액션(Action)은 컴포넌트가 상태를 변경할 때, 전달할 데이터를 의미합니다. 일종의 주문서와 같은 역할입니다.

Action은 자바스크립트 객체 형식으로 표현됩니다.

{
  type: 'ACTION_CHANGE_THEME',
  payload: {
    value: 'light',
  }
}

Reducer

액션(Action)을 스토어(Store)에 전달하기 위해서는 전달자가 필요합니다. 그 역할을 수행하는 것이 리듀서(Reducer)입니다.

dispatch() 메소드를 통해 액션을 전달하고 리듀서를 호출합니다.

  1. Action이 dispatch() 메소드에 전달
  2. dispatch(액션)을 통해 Reducer 호출
  3. Reducer는 새로운 Store 생성

2.3 Redux Toolkit

Redux Toolkit은 Redux에서 공식적으로 제공하는 가이드라인입니다. Redux는 비교적 러닝 커브(Learning Curve)가 높은 것으로 알려져 있는데, 너무 높은 자율성으로 설정하는 것이 복잡하기 때문입니다.

Redux Toolkit은 아래 3개의 문제점을 해결하기 위해 만들어졌습니다

  • "저장소를 설정하는 것이 너무 복잡하다"
  • "쓸만하게 되려면 너무 많은 패키지들을 더 설치해야 한다"
  • "보일러플레이트 코드를 너무 많이 필요로 한다"

계속해서 Redux Toolkit을 활용한 상태 관리 프로세스를 소개합니다.


3. Redux Toolkit Process

프로젝트에 reduxredux-toolkit을 추가하여 상태를 관리하는 프로세스를 소개해보고자합니다.


3.1 Install Package

먼저 프로젝트에 redux 모듈을 설치해줍니다.

# redux core 설치
yarn add redux react-redux

# redux type 설치
yarn add -D @types/react-redux

# redux toolkit 설치
yarn add @reduxjs/toolkit

3.2 기본 설정

store 생성

store로 사용할 index.ts 파일을 생성합니다.

// 파일 경로: src/store/index.ts

import {configureStore, createSlice} from '@reduxjs/toolkit'

export default configureStore({
    reducer: {}
})

store 적용

루트 파일에 react-redux 모듈의 Provider 컴포넌트를 이용하여 store를 등록합니다.

💡 Provider

Provider 하면 무언가 떠오르지 않나요?
react-redux 모듈은 내부적으로 Context API를 사용합니다.

// 파일 경로: src/index.tsx

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'

import { App } from './App'
import {store} from "./store/index";

const root = ReactDOM.createRoot(
    document.getElementById('root') as HTMLElement
);

root.render(
  <Provider store={store}>
    <App />
  </Provider>,
)

3.3 Slice 설정

Slice는 일종의 Store에서 떨어져 나온 조각이란 의미로, slice를 하나의 상태 값을 관리합니다.


Slice의 기본 구조

import {createSlice} from "@reduxjs/toolkit";

// 1. createSlice를 이용하여 Slice 생성
const slice = createSlice({
  	// 2. slice 이름 지정
    name: 'name',
  	// 3. 초기 상태 지정
    initialState: {},
  	// 4. 상태를 변경할 reducer 구현
    reducers: {}
})

export default slice;

Slice Example: Counter

대표적인 Counter Slice를 만들어 보았습니다.

// 파일 위치: src/store/slice/counter.ts

import {createSlice, PayloadAction} from "@reduxjs/toolkit";

const counterSlice = createSlice({
    name: 'counter',
    initialState: {
        value: 0
    },
    reducers: {
      	// Payload가 없을 경우
      	plus: (state) => {
          	state.value ++;
        },
      	// Payload가 있을 경우
        plus: (state, action: PayloadAction<number>) => {
            state.value += action.payload
        },
        minus: (state, action: PayloadAction<number>) => {
            state.value -= action.payload
        },
    }
})
export const {plus, minus} = counterSlice.actions
export default counterSlice;

Slice 등록

store에 slice를 등록합니다.

// 파일 위치: src/store/index.ts

import {configureStore} from '@reduxjs/toolkit'
import counterSlice from "./slice/counter";

export const store = configureStore({
    reducer: {
        counter: counterSlice.reducer,
    }
})
export type RootState = ReturnType<typeof store.getState>

3.4 Store 호출

Store Example: Counter

// 파일 위치: src/App.tsx

import React from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "./store/index";
import {minus, plus} from "./Store/slice/counter";
import {Button} from "@mui/material";

const App = () => {
    const dispatch = useDispatch();
    const count = useSelector((state: RootState) => state.counter.value)
    return (
        <div>
            <div>
                value: {count}
            </div>
            <div>
                <Button onClick={() => dispatch(plus(1))}>Add</Button>
                <Button onClick={() => dispatch(minus(1))}>Minus</Button>
            </div>
        </div>
    );
};

export default App;

아래와 같은 화면이 만들어집니다.

Redux Hooks

useSelector Hooks를 이용해 store에 저장된 state를 가져오고,
useDispatch를 사용해 변경할 값을 reducer에 전달해줍니다.

import { useDispatch, useSelector } from 'react-redux';

const dispatch = useDispatch();
const count = useSelector((state: RootState) => state.counter.value)

💎 References


💎 마치며

redux를 처음 접했을 때는 라이브러리를 이해하기가 너무 어렵다고만 생각이 들었습니다.😇

지금와서 생각해보면 가장 큰 문제는 상태에 대한 이해 부족입니다. 라이브러리를 무작정 쓰기 전에 컴포넌트 상태와 전역 상태가 왜 필요한지를 먼저 살펴볼 필요가 있습니다

프론트를 개발하면서 컴포넌트의 상태 관리에 대해 이해도가 생겨났고, redux에서 가이드라인을 잡아주는 redux-toolkit 모듈을 추가로 개발해주는 덕택에 리덕스를 빠르게 활용할 수 있게 되었습니다 🙏🙏

결론. 모두 툴킷쓰세요~~~🥺

profile
Java, Spring 기반 풀스택 개발자의 개발 블로그입니다.

0개의 댓글