Redux

h-a-n-a·2023년 6월 12일
1

📚library

목록 보기
1/4
post-thumbnail

리덕스란?

javascript 상태관리 라이브러리

리덕스는 특히 React와 함께 사용될 때 유용하지만 다른 라이브러리나 프레임워크와도 사용이 가능하다.

리덕스를 왜 쓸까?

공식문서에 써있는 내용은 다음과 같다.

Redux is a predictable state container for JavaScript apps.

It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.

예측 가능하고 일관적인 동작? 이를 이해하기 위해선 MVC패턴FLUX패턴에 대한 이해가 필요하다.

MVC 패턴

MVC란, Model-View-Controller의 약자로 애플리케이션을 세 가지 역할로 구분한 개발 방법론이다.

  • Model: 데이터를 보관하는 역할
  • Controller: 데이터에 대한 수정, 조회 등의 역할
  • View: 데이터를 화면에 보여주는 역할

아래의 그림처럼 사용자가 Controller를 조작하면 => Controller는 Model을 통해 데이터를 가져오고 => 그 데이터를 바탕으로 View를 통해 시각적 표현을 제어하여 사용자에게 전달하게 된다.

MVC 패턴의 장점

이러한 패턴을 성공적으로 사용하면, 사용자 인터페이스로부터 비즈니스 로직을 분리하여 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직이 서로 영향없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있게 된다. 즉, 비즈니스 로직과 UI로직을 분리하여 유지보수를 독립적으로 수행할 수 있게 된다.

MVC 패턴의 한계

그런데 Controller가 Model도 관리하고, View도 관리해야하는 구조에서, 만약에 다루는 데이터의 종류와 화면이 훨씬 많아지게 된다면!? Controller에 다수의 Model과 View가 복잡하게 연결되어 있는 상황이 발생하게 될 수 있다.
하나의 Model의 수정에 따라 여러 View가 수정되는가 하면, 반대로 View에서의 상호작용에 따라 여러 Model이 수정될 수도 있다.
즉, 정확히 어떤 데이터가 어떤 View를 통해서 수정이 되고, 어떤 View가 정확히 어떤 모델로부터 데이터를 받는지 그 흐름을 추적하는 것이 복잡해진다.

FLUX 패턴

이러한 복잡성을 해결하고자 등장한 것이 바로 Flux 패턴이다. MVC 패턴의 복잡성을 해결하기 위해, Flux 패턴에서는 데이터가 한 방향으로만 흐르도록 했다.

위는 기본적인 Flux의 형태로, 어떤 Action이 발생하면, Dispatcher에서 이를 받아와 해석한 후 Store에서 저장된 정보에 변경을 가하고, 그 결과가 View로 다시 전달되도록 하는 흐름을 갖고 있다.

그런데 모바일에서 터치를 하는 것처럼, 웹에서도 사용자가 View를 통해서 클릭 같은 액션을 발생시킬 수 있다. 그런 경우도 고려하면 아래와 같은 흐름이 만들어진다.

이렇게 할 경우 action 객체만 잘 따라가도, 정확히 어떤 데이터 변화가 일어나고 있는지 추적할 수 있게 된다. View에서 사용자와의 상호작용에 따라 새로운 Action 객체가 생성되더라도 항상 Dispatcher를 통하기 때문에 데이터의 흐름이 한 방향으로 흐르게 되는 것이다.

자, 이제 다시 리덕스로 돌아오자.
그래서 리덕스가 뭐고, 왜 쓴다는건데?

본격적인 리덕스 소개

지금까지 위에서 MVC패턴과 Flux 패턴을 소개한 이유는, 리덕스는 Flux패턴을 기반으로 한 것이기 때문이다.

리액트에서 리덕스를 사용하는 이유

기존 리액트 같은 경우, 자식 컴포넌트들간의 다이렉트 데이터 전달은 불가능했기에 부모 컴포넌트로 'state 끌어올리기'나 불필요한 props drilling 등이 이슈가 되면서 전역적인 state 관리법이 필요해졌다.

리덕스는 store 단 한 곳에서 모든 상태 관리가 가능하다. 이 스토어에 모든 어플리케이션 상태가 저장된다. 상태의 변경은 액션을 발생시키고 리듀서가 액션을 처리해서 이전 상태와 액션을 받아 새로운 상태를 반환한다.

또한 리덕스는 단방향 데이터 흐름을 따르기 때문에 상태 변화를 예측하기 쉽고, 디버깅도 용이하다. 또한 여러 컴포넌트 간의 상태를 공유하거나 상태 변경 로직을 중앙 집중화하여 관리하기 때문에 코드 유지보수성을 높여준다.

위에서 읽었던 공식문서의 예측 가능하고 일관적인 동작이라는 글이 이제는 이해가 될 것이다!
그럼 Redux의 구조에 대해 살펴보도록 하자.

Store, Action, Reducer의 의미와 특징

Store(스토어)

Store는 전역 상태의 컨테이너. state가 관리되는 오직 하나뿐인 저장소이다.

  • 컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담는다.
  • 절대로, 리덕스 스토어 내에 있는 상태를 직접 수정하거나 변경해서는 안된다.
  • 컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.

Action(액션)

어떤 action을 취할 것인지 정의해놓은 type이 필수로 지정된 객체

  • 보통 action 객체를 생성하는 함수 action creator를 만들어 사용
  • 액션은 앱에서 스토어에 운반할 데이터를 말한다.(주문서)
  • 액션은 자바스크립트 객체 형식으로 되어있다.

Reducer(리듀서)

Dispatch에서 전달받은 Action 객체의 type에 따라서 상태를 변경시키는 함수

  • Action을 Store에 바로 전달하는 것이 아니다.
  • Action을 Reducer에 전달해야 한다.
  • Reducer가 주문을 보고 Store의 상태를 업데이트하는 것이다.
  • Action을 Reducer에 전달하기 위해서는 dispatch()메소드를 사용해야 한다.

즉, Action 객체가 dispatch() 메소드에 전달되고
dispatch()를 통해 Reducer를 호출하고
Reducer는 새로운 Store를 생성하는 것이다.

Redux의 기본 개념: 3가지 원칙

Single source of truth

The global state of your application is stored in an object tree within a single store.

  • 데이터를 저장하는 Store라는 단 하나뿐인 공간에서 동일한 데이터를 들고 와야 한다.

State is read-only

The only way to change the state is to emit an action, an object describing what happened.

  • 상태는 읽기 전용으로 직접 변경할 수 없고, Action 객체가 있어야만 변경할 수 있다.

Changes are made with pure functions

To specify how the state tree is transformed by actions, you write pure reducers.

  • 액션을 통해 상태트리를 변환되는 방식을 지정하고 싶다면, 순수 함수(리듀서)를 작성해야 한다.
  • Store(스토어) -Action(액션) - Reducer(리듀서)

생성 프로세스

Action

  • 액션 이름 정의
    -액션 이름은 고유해야 한다

  • 액션 생성 함수 생성
    -액션 객체는 type을 포함한 객체이다

const ADD_SUBSCRIBER = 'ADD_SUBSCRIBER'
const addSubscriber = () =>{
	return {
    	type: ADD_SUBSCRIBER
    }
}        

Reducer

  • 들어오는 액션에 따라 어떻게 상태를 변경해 새로운 상태를 반환할지 알려주는 역할을 한다.
    -초기값 생성
const initialState={
	subscribers: 100
}
const reducer = (state=initialState, action) =>{
	swith (action.type){
    	case ADD_SUBSCRIBER;
        	return {
            	...state,
                subscribers: state.subscribers+1
      		// 기존state복사해서 새로운 객체 반환해야 함!
            }
        default: return state;
    }
}
  • 리듀서 함수 정의
  • 리듀서는 oldState와 액션함수를 받는다
    -액션이 발생되면 리듀서가 oldState와 action을 받아와 newState 값을 반환한다

Store

  • store 생성
    -store는 createStore 함수 사용
import {createStore} from 'redux'
const store = createStore(reducer);
//작성한 reducer을 토대로 스토어 생성
  • dispatch: store에 등록한 reducer에 액션객체를 전달해 store를 제어할 수 있다. 스토어 내장 함수 dispatch를 사용해서 파라미터로 액션을 넘겨주면 된다.
store.dispatch(addSubscriber())
  • subscribe: subscribe 함수 안에 특정 함수를 넣어두면, 액션을 통해 state가 변경될 때마다 특정 함수가 실행된다.
store.subscribe(()=>{
  console.log(store.getState())
  // getState:현재 store에 있는 상태를 출력한다.
})
store.dispatch(addSubsriber()) //({subscribers:101})
store.dispatch(addSubsriber()) //({subscribers:102})
store.dispatch(addSubsriber()) //({subscribers:103})
store.dispatch(addSubsriber()) //({subscribers:104})

React Redux

리액트에서 리덕스를 사용하다보면 당연히 코드량이 많기에 src 폴더 안에 actions, reducers, store, components, pages 폴더로 분할해 작성하게 된다. 그리고 모든 reducers들을 모아 reducer.js 에 합쳐주면 된다.

import { combineReducers } from 'redux';
import itemReducer from './itemReducer';
import notificationReducer from './notificationReducer';

const rootReducer = combineReducers({
  itemReducer,
  notificationReducer
});

export default rootReducer;

그리고 store 폴더 안에 스토어를 생성한 후에 리듀서를 등록하면 끗!

// store 폴더 안에 store.js => 스토어를 생성한 다음 리듀서를 등록한다.

import { createStore } from "redux";
import rootReducer from '../reducers/index';

const store = createStore(rootReducer);

export default store;

하위 컴포넌트에서 store에 접근하는 방법

import React from 'react';
import { addToCart } from '../actions/index'; // 액션 가져오기
import { useSelector, useDispatch } from 'react-redux'; // 리덕스 훅 가져오기

function ItemListContainer() {
  const state = useSelector(state => state.itemReducer); // 스토어에서 itemReducer로 등록된 상태 가져오기
  const { items, cartItems } = state; // 상태가 객체이고 구조분해 한다.
  const dispatch = useDispatch(); // 상태 업데이트 할 dispatch() 메소드 가져오기

  // 클릭 이벤트
  const handleClick = (item) => {
     dispatch(addToCart(item.id)); // dispatch()를 이용해서 action을 리듀서에 전달
  }

  ... 생략
}

그러나 리덕스는 action value, action creator, initialState, reducer 등 작성해야 할 코드량이 너무 많다보니 여러 불만이 속출(?)하였다. 그래서 코드는 더 적고 사용법은 더 간편해진 리덕스 툴킷이 등장하였으니!

그것은 다음 시간에 알아보도록 하겠다~!!


오늘의 일기:

이전에 멘토님이 말씀하시길, 라이브러리든 프레임워크든 사용할 때 공식문서 보면 개발자들이 어떤 이유에서 자기들이 피땀흘려 거북목 되어가며 이걸 만들었는지 다 써있으니 제발 읽어보라고 했던 게 생각났다.

확실히 그냥 유명하다고 사용하는 게 아니라 그 배경을 알고나니 해당 라이브러리에 대한 이해도 높아졌고, 활용도 잘 할 수 있을 것 같다는 생각이 들었다.

시간이 좀 걸리긴했지만 공식문서도 읽으면서 천천히 정리할 수 있어서 좋았다!


참고사이트: Redux의 데이터 흐름과 Flux패턴 by Summer
리액트 앱에서 Redux로 상태관리 하기 by 365kim
Redux공식문서
Redux(리덕스)란? (상태 관리 라이브러리) by 하나몬
리덕스(Redux)를 왜 쓸까? 그리고 리덕스를 편하게 사용하기 위한 발악 (i) by velopert
리액트 리덕스 (Redux) 튜토리얼 순한맛 1/2 by 데브리
아마 이게 제일 이해하기 쉬울걸요? React + Redux 플로우의 이해 by carrot useless

profile
하루하루가 연습이니 내일은 더 강해질 겁니다

0개의 댓글