개발 공부한 것들/SeSSAC 웹 풀스텍 과정 회고록

[SeSSAC] 웹 풀스택 영등포 5기 과정_REACT(7)_Redux

geograp 2023. 10. 22. 00:49

안녕하세요

이번 시간에는 Redux에 대해 배웠습니다.

 

0. Redux란?

 

JavaScript의 상태 관리 라이브러리입니다.

주로 React와 함께 사용되지만, 다른 JavaScript의 프레임워크나 라이브러리에서 사용 가능합니다.

 

Redux의 목표는 애플리케이션의 상태를 예측 가능하고 효율적으로 관리하는 것입니다.

즉, 한마디로 props 지옥을 벗어나는 것입니다.

출처 https://www.slideshare.net/binhqdgmail/006-react-redux-framework

 

컴포넌트 수가 많아지는 대형 프로젝트에서는

왼쪽 그림처럼 state를 전해주기 위한 props를 엄청나게 많이 써야 하는 경우가 발생하죠.

 

Redux를 사용하게 된다면,  *전역*으로 상태를 관리할 수 있어서,

store라는 곳에서 언제든 꺼내쓸 수가 있습니다. (오른쪽 그림과 같이)

 


1. Redux의 핵심 개념

 

1-1. Store (스토어)

  • Redux에서 상태를 관리하는 중심 역할을 하는 객체
  • 애플리케이션의 모든 상태가 저장되는 저장소
  • getState, dispatch, subscribe과 같은 메서드를 제공함
  • 한 개의 프로젝트에 하나의 스토어만 가질 수 있음
  • 스토어에 있는 데이터는 컴포넌트에서 직접 조작하지 않고 리듀서 함수를 통해 간접적으로 사용

1-2. Action (액션)

  • 상태 변경을 설명하는 일반적인 객체
  • 반드시 type 필드를 가지고, 필요에 따라 추가 데이터를 포함(payload)할 수 있음
  • 리듀서가 수행할 작업을 설명해 줌

 

1-3. Reducer (리듀서)

  • 액션을 처리하고 새로운 상태를 반환하는 순수 함수
  • 이전 상태와 액션을 인자로 받아 새로운 상태를 생성함
  • 스토어가 리듀서를 통해 상태를 업데이트함
  • Http요청, 데이터 저장 같은 건 하면 안 됨

 

1-4. Dispatch (디스패치) 

  • 스토어를 통해 액션을 전달하는 메서드

 

 

이렇게 개념만 보면 섭섭하니까 실제 코드들을 가져와서 보도록 하겠습니다.


 

 

2. Redux 시작하기

 

Redux를 시작하려면 먼저 Redux 모듈을 설치해야 합니다.

npm install redux react-redux @reduxjs/toolkit

 

그리고 이 설치한 리덕스를 사용하기 위해 리액트에 적용해야 합니다.

index.js 내부

이 코드는 이 리액트의 모든 요소를 아우르는 index.js 파일에서 Redux를 적용하고 있는 모습입니다.

 

import 되는 함수들도 헷갈리니, 하나하나 정리해 보도록 하겠습니다.

 

import { Provider } from 'react-redux';

React에서 Redux 스토어(저장소)를 사용하려면 Provider 컴포넌트를 가져와야 합니다.

이 컴포넌트는 Redux 스토어를 애플리케이션에 제공하는 역할을 합니다.

 

import { configureStore } from '@reduxjs/toolkit';

Redux Toolkit에서 제공하는 configureStore 함수를 가져오고 있습니다.

이 configureStore함수는 Redux 스토어를 설정하는 데 사용합니다.

Redux Toolkit은 간단하고 효율적으로 Redux를 사용하도록 도와주는 도구 모음입니다.

 

import { composeWithDevTools } from 'redux-devtools-extension';

Redux DevTools확장을 사용하여 개발 중에 Redux 동작을 모니터링해 주는 도구입니다.

 

import rootReducer from './store';
import Practice from './Practice';

→ 각각의 파일에 있는 리듀서와 컴포넌트를 가져옵니다.

rootReducer는 제가 store파일에 만든 주요 리듀서이고, Practice는 제가 실습용으로 만든 컴포넌트입니다.

 

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

React18 이후의 새로운 기능 중 하나입니다.

더 빠르고 반응적인 애플리케이션을 만들 수 있도록 도와주는 도구입니다.

 

사용자 경험을 향상하기 위해 병렬적으로 여러 작업을 처리하는 Concurrent Mode 기능이 있습니다.

Current Mode 내에서 createRoot 메서드를 사용한 것입니다.

 

createRoot 메서드는 애플리케이션의 루트를 생성하고 초기 렌더링을 시작하고 이후에도 업데이트를 지원합니다.

 

const store = configureStore({ reducer: rootReducer }, composeWithDevTools());

이 코드는 Redux Toolkit을 사용하고 Redux의 스토어를 구체적으로 설정하는 부분입니다.

 

configureStore 함수는 초기 스토어 설정을 구성합니다.

여기서는 매개 객체로 { reducer: rootReducer }가 전달되어 스토어는 이 리듀서를 사용하여 액션을 처리합니다.

 

composeWithDevTools 함수는 개발 중에 Redux 스토어의 상태 및 액션을 쉽게 추적하도록 도와줍니다.

스토어의 동작을 모니터링하고 디버깅하기 위해 스토어를 확장하기도 합니다.

 

root.render(
    <Provider store={store}>
      <App4 />
      <hr />
      <Practice />
    </Provider>
);

  여기서 핵심은 App4나 Practice와 같은 컴포넌트들을 감싼<Provider></Provider>입니다.

여기서 Provider에 propsstore가 전달되어 모든 컴포넌트에서 store가 사용가능한 것입니다.

 

그리고 추가로, chrome에서 확장 프로그램으로

Redux DevTools를 설치해서 리액트 앱에 적용하면 Redux 스토어에 저장된 상태를

간편하게! 확인할 수 있습니다

 

Redux DevTools

// 리액트 앱에 적용하기
npm install redux-devtools-extension

 


3. Redux 사용하기

 

먼저, 현재 제가 소개해드릴 간단한 실습은, 

Redux를 사용해서 input창에 숫자를 입력하고 입금 버튼을 선택한다면 통장잔액을 +로 변화시키고

출금 버튼을 선택한다면 통장잔액을 그만큼 -로 변화시키는 실습입니다.

 

 

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

실습 메인 코드

먼저, 중심부가 되는 파일 먼저 살펴보도록 하겠습니다.

 

여기서 전에 공부했던 useState 훅을 input에 들어온 금액을 위해 사용하고 있고,

그 외 redux 관련 훅인 useSelector와 useDispatch를 사용하고 있습니다.

 

3-1. useSelector

  • Redux 스토어에서 상태값을 선택하는 데 사용되는 훅
  • Redux 스토어의 상태를 읽고 불러와서 컴포넌트에서 사용가능하게 함
 const money = useSelector((state) => state.money.money);

이 코드는 현재 store에 있는 money의 money항목을 불러오고 있습니다.

 

이 코드가 store에서 money항목을 찾아올 수 있는 이유도 알아보도록 하겠습니다.

 

저는 src 폴더 내 store 폴더를 만들고 그 폴더 내에서 moneyReducer.js라는 파일을

만들었고, 이 파일은 순전히 돈관리를 하는 store(reducer)를 담당하고 있습니다.

 

이 파일 내에서 전달하는 상태 값에 따라 입금인지 출금인지 구분하여 값을 변경하고 있습니다.

그래서 그 store의 money가 변경된 상태값을 money라는 변수에 넣어서 활용하고 있습니다.

 

 

3-2. useDispatch

  • Redux 스토어에 액션을 디스패치하기 위해 사용되는 훅
  const dispatch = useDispatch();

디스패치는 주로 이렇게 훅을 변수에 담아서 활용한다고 합니다.

 

const Deposit = (num) => ({type: 'DEPOSIT', payload: num});
const Withdrawal = (num) => ({type: 'WITHDRAWAL', payload: num});

<button onClick={() => {dispatch(Deposit(parseInt(inputNum)))}}>입금</button>
<button onClick={() => {dispatch(Withdrawal(parseInt(inputNum)))}}>출금</button>

이 실습에서는 입금이라는 버튼을 클릭했을 때 dispatch를 활용해서

type은 'DEPOSIT'이고 payload로 매개 변수 num을 전달하고 있습니다.

 

마찬가지로, 출금이라는 버튼을 클릭했을 때 dispatch를 활용해서

type은 'WITHDRAWAL'이고 payload로 매개변수 num을 전달하고 있습니다.

 

 

그렇게 된다면, dispatch함수를 통해 store로 type과 payload가 전달됩니다.

 

store내 moneyReducer

여기서 입금이 눌러졌다면 type을 통해 case 'DEPOSIT'이 들어가게 되어

moeny가 업데이트되는 것입니다!

 

 

여기서 저는 이 case 'DEPOSIT'을 어떻게 이 dispatch가 찾아가는지 신기할 따름이었는데요,

일반적으로 store 폴더 내에서는 index.js와 같은 하나의 대표 파일에

모든 Reducer와 변수를 넣어서 관리한다고 합니다.

 

store 폴더 내 index.js 파일

여기서 제일 중요한 개념은 combineReducers입니다.

 

3-3. combineReducers

  • 여러 개의 리듀서들을 하나로 합칠 때 사용되는 함수
  • 주어진 리듀서들을 모으고 조합하여 하나의 root리듀서를 생성
  • 이 리듀서들의 상태 트리를 통해 dispatch로 type이 들어오면 알맞은 리듀서로 연결해 주는 역할

 

이 루트리듀서를 통해 type에 알맞게 리듀서 처리를 해준다고 합니다. 

해서, 만약 같은 store 폴더 내에서 같은 type의 이름을 갖게 된다면,

해당 이름의 type을 가진 모든 리듀서에게 전달하는 상황이 발생한다고 합니다.

 

 

 

 

이렇게 리액트의 Reducer의 개념과 전체적인 흐름을 알아보았습니다 :D