React

[React] 리액트 Redux 사용하기 + useState / Redux / Redux toolkit 비교하기

solfa 2024. 2. 29. 02:51

 

사실 상태관리를 이렇게 빨리 공부할 마음은 없었다... 핳

훅도 제대로 못 쓰는 내가 상태관리를?! 그것도 리덕스를?!!!

context랑 recoil만 공부하고 다른 거 더 깊이 공부하고 redux를 쓰던 zustand를 쓰던 jotai를 쓰던 하려고 했는데...

과제에서 redux를 쓰라네? ㅠㅠ

이론 공부 쓱 하고 최소한의 정도로만 공부를 해봐야겠다는 생각에 오늘도,,, (뚠뚠) 솔미는,,, (뚠뚠) 글을 쓰네,,, (뚠뚠) 

공부 할 것들... redux 공부 -> useReducer -> useRef


Redux란?

Redux는 JavaScript 애플리케이션에서 상태 관리를 위한 패턴과 라이브러리이다. Redux의 주요 목표는 애플리케이션의 상태를 예측 가능하고 효율적으로 관리하는 것이다.

 

Redux의 작동 방식

Redux는 단방향 데이터 흐름을 따른다. 즉, 액션을 디스패치하면 해당 액션에 따라 상태가 업데이트되고, 이에 따라 UI가 업데이트된다.

 -> 이러한 방식으로 데이터의 흐름이 예측 가능하고 디버깅하기 쉬워진다.

 

Redux의 핵심 개념

  1. Store: 애플리케이션의 상태를 보유하고 있는 단일 객체다. Redux에서는 애플리케이션의 전체 상태가 하나의 스토어에 저장된다.
  2. Actions: 상태 변경을 나타내는 객체다. 주로 JavaScript 객체로 표현되며, type 속성을 통해 어떤 종류의 작업인지 식별된다.
  3. Reducers: 상태 변경을 처리하는 순수 함수이다. 이전 상태와 액션을 받아서 새로운 상태를 반환한다. Redux에서는 모든 상태 변경은 리듀서를 통해 이루어진다.
  4. Dispatch: 액션을 리듀서에 전달하여 상태 변경을 일으킨다. 액션을 디스패치하면 해당 액션을 처리하는 리듀서가 호출되어 상태를 업데이트한다.

Redux 중요한 3가지 원칙

  1. 스토어는 글로벌 상에서 하나만 존재해야한다.
     => 하나의 스토어에 모든 상태가 관리되어야 한다.
  2. 상태는 읽기 전용으로 액션 객체에 전달해야지만 변화할 수 있다.
  3. 변화는 순수함수로 작성해야한다.
    순수 함수 : 외부의 개입 없이 동일한 인풋값에 따라 항상 동일한 결과를 내보내는 함수
    리듀서 : 이전 상태의 액션을 받아 다음 상태를 반환하는 순수 함수! 이전 상태 값을 변경하는 것이 아니라 새로운 상태값을 반환한다.

 

Redux의 데이터 흐름

액션 -> 디스패치 -> 리듀서 -> 스토어 순으로 단방향으로 데이터가 흐른다.

  1. 액션(Action)
    액션은 상태 변경을 일으키는데 사용되는 일종의 이벤트이다. 보통 JavaScript 객체로 표현되며 최소한의 정보인 type 속성을 가지고 있다.
    Ex) 사용자가 로그인 버튼을 클릭하면 "USER_LOGIN"과 같은 타입의 액션을 생성할 수 있다.
  2. 디스패치(Dispatch)
    액션을 발생시키는 과정! dispatch 함수를 사용하여 액션을 스토어에 전달한다.
    Ex) dispatch({ type: 'USER_LOGIN', payload: { username: 'example', password: 'example' } })와 같이 액션을 디스패치할 수 있다.
  3. 리듀서(Reducer)
    리듀서는 이전 상태와 액션을 받아서 새로운 상태를 반환하는 함수, 순수 함수로 작성되어야 함
    액션의 타입에 따라 다른 작업을 수행하고 새로운 상태를 반환한다. (if-else나 switch 사용)
    Ex) 'USER_LOGIN' 액션이 발생했을 때, 리듀서는 인증된 사용자 정보를 상태에 저장할 수 있다.
  4. 스토어(Store)
    스토어는 애플리케이션의 상태를 보유하고 있는 객체이고 모든 상태는 단일 스토어에 저장된다.
    스토어에는 getState(), dispatch(action), subscribe(listener)와 같은 메서드가 있다.
    Ex) createStore(rootReducer)와 같이 리듀서를 이용하여 스토어를 생성할 수 있다.

 

Redux 사용법

 

1. 모듈 설치하기

// redux 설치
npm i redux 
yarn add redux

// react에서 redux 사용할 때 추가 설치
npm install redux react-redux
yarn add react-redux

 

2. redux-toolkit 설치하기

npm i @reduxjs/toolkit
yarn add @reduxjs/toolkit

 

이론 백날 해봤자 직접 해보는 것만 못하니... 바로 예제를 만들어보겠습니당

 


카운터 예제로 useState와 Redux, Redux-toolkit의 차이 알아보기

1. useState() 사용하기

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
      <button onClick={() => setCount(count - 1)}>감소</button>
    </div>
  );
}

export default Counter;

 

useState를 사용한 state값은 컴포넌트 내부에서만 사용할 수 있고 밖에서 사용하고 싶을 땐 props로 값을 전달해줘야함!
하지만...
props 사용시 문제점

  1. 컴포넌트의 깊이가 깊어질 수록 추적하기 힘들고 props drilling이 발생할 수 있음
  2. props로 인한 상태 변경이 일어났을 때 예상치 못한 상태 변경이일어날 수 있음

 

2. Redux 사용하기

먼저 폴더 구조는 다음과 같다

project/
│
├── src/
│   ├── actions/            # 액션을 정의하는 디렉토리
│   │   └── actions.js     # 액션 타입을 정의하고 액션 생성자를 포함하는 파일
│   │
│   ├── reducers/           # 리듀서를 정의하는 디렉토리
│   │   └── reducers.js    # 상태 변경을 처리하는 리듀서를 포함하는 파일
│   │
│   ├── store/              # 스토어를 생성하는 디렉토리
│   │   └── store.js       # Redux 스토어를 생성하는 파일
│   │
│   ├── components/         # React 컴포넌트를 정의하는 디렉토리
│   │   ├── App.js         # 최상위 컴포넌트
│   │   └── CounterComponent.js   # 카운터 컴포넌트
│   │
│   └── index.js            # 애플리케이션 진입점
│
└── package.json            # 프로젝트 의존성 및 스크립트 정의

 

2-1. action 만들기

먼저 액션을 정의한다. 액션은 상태 변경을 나타내는 객체이고 각 액션은 type 속성을 가지고 있다.

// actions.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

 

2-2. reducer 만들기

리듀서는 이전 상태와 액션을 받아서 새로운 상태를 반환하는 함수이다. if-else나 switch를 사용한다.

// reducers.js
import { INCREMENT, DECREMENT } from './actions';

const initialState = { // 기본값
  count: 0
};

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

export default counterReducer;

 

2-3. store 만들기

리덕스에서는 모든 상태가 하나의 스토어에 저장된다. createStore 함수를 사용하여 스토어를 생성한다.

여기서는 reducers.js에 정의한 리듀서를 사용했다.

// store.js
import { createStore } from 'redux';
import counterReducer from './reducers';

const store = createStore(counterReducer);

export default store;

 

2-4. 카운터 컴포넌트 (컴포넌트에서 리듀서 사용하기)

스토어의 상태를 컴포넌트에 연결하여 사용하려면 react-redux 라이브러리의 connect 함수를 사용해야 한다.

나는 useDispatch와 useSelector함수를 사용해 store에 접근하는 방법을 사용했다.

 

useSelector : Redux 스토어의 상태를 읽어오는 데 사용

useDispatch : 액션을 디스패치하는 데 사용

// CounterComponent.js
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { INCREMENT, DECREMENT } from './actions';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>
      <button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>
    </div>
  );
}

export default CounterComponent;

 

2-5. app.js 수정하기

Provider로 컴포넌트를 감싸주어야 한다!

// App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import CounterComponent from './CounterComponent';

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <CounterComponent />
      </div>
    </Provider>
  );
}

export default App;

 

+ 여러 reducer를 사용하는 경우에는 리덕스를 묶어주는 rootReducer이 있어야 한다!


 

3. Redux-toolkit 사용하기

 

폴더 구조

project/
│
├── src/
│   ├── features/              # 각 기능별로 Slice를 정의하는 디렉토리
│   │   └── counterSlice.js    # 카운터 Slice
│   │
│   ├── store/                 # Redux 스토어를 생성하는 디렉토리
│   │   └── store.js           # Redux 스토어 설정 파일
│   │
│   ├── components/            # React 컴포넌트를 정의하는 디렉토리
│   │   ├── App.js             # 최상위 컴포넌트
│   │   └── CounterComponent.js # 카운터 컴포넌트
│   │
│   └── index.js               # 애플리케이션 진입점
│
└── package.json               # 프로젝트 의존성 및 스크립트 정의

 

3-1. slice 만들기

Redux Toolkit에서는 Slice를 사용하여 액션과 리듀서를 함께 정의한다.

Slice는 액션 생성자와 리듀서를 모두 포함하며 Redux 스토어의 상태를 변경하는 데 사용된다.

// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 0,
  },
  reducers: {
    increment: state => {
      state.count += 1;
    },
    decrement: state => {
      state.count -= 1;
    },
  },
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlice.reducer;

 

3-2. store 만들기

configureStore 함수를 사용하여 Redux 스토어를 생성한다.

이 함수는 Redux 스토어를 설정하고 DevTools와 같은 중간웨어를 쉽게 통합할 수 있는 기능을 제공한다.

// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export default configureStore({
  reducer: {
    counter: counterReducer,
  },
});

 

3-3. 카운터 컴포넌트 만들기

React 컴포넌트에서 useSelector 훅과 useDispatch 훅을 사용하여 Redux 스토어의 상태를 읽고 업데이트한다.

필요한 경우 useDispatch를 통해 액션을 디스패치할 수 있다.

// CounterComponent.js
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { increment, decrement } from './counterSlice';

function CounterComponent() {
  const count = useSelector(state => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>
  );
}

export default CounterComponent;

 


 

Redux와 Redux Toolkit의 차이점

  1. 구문의 간소화
    Redux Toolkit은 Redux의 구문을 단순화하고 보일러플레이트 코드를 줄여준다. -> 코드를 더 짧고 읽기 쉽게 만들어줌
  2. 기본 설정 포함
    Redux Toolkit은 기본적인 설정을 포함하여 Redux를 더 빠르게 시작할 수 있도록 도와준다. Redux Toolkit에는 스토어를 생성하기 위한 configureStore 함수와 액션 및 리듀서를 정의하기 위한 createSlice 함수가 포함되어 있다는 점~
  3. Immutable 업데이트
    Redux에서는 상태를 업데이트할 때 불변성을 유지해야 한다. ==  상태를 직접 수정하는 것이 아니라 새로운 상태를 생성하여 업데이트해야 한다는 말! Redux Toolkit은 immer라는 라이브러리를 사용하여 불변성을 유지하고 개발자가 더 간편하게 상태를 업데이트할 수 있도록 도와준다.
  4. DevTools 통합
    Redux Toolkit은 개발자 도구와의 통합을 쉽게 지원한다. Redux DevTools Extension이라는 Redux 애플리케이션의 상태 변화를 시각적으로 추적하고 디버깅해주는 개발자 도구의 브라우저 확장 프로그램이 있는데 이걸 자동으로 연동해주어서 디버깅 과정을 편리하게 만들어준다.
  5. 성능 최적화
    Redux Toolkit은 성능 최적화를 위해 내부적으로 몇 가지 최적화를 수행한다. 불변성을 강제하거나 메모이제이션으로 이전 값을 재사용하고 createReducer와 같은 유틸리티 함수를 제공하여 리듀서 코드를 간단하게 작성할 수 있도록하는 등의 최적화를 수행한다.

 


 

이제 공부한 redux를 토대로 간단한 todo list 예제를 만들어보자!

글 >>

728x90