✏️/React

Redux 직접 만들어보기

naoo 2022. 9. 26. 03:24

Redux 깃허브를 한번 분석해보면 좋다는 말을 들어서 이번에 한번 보게 되었다.

createStore.ts 파일을 읽어보며 리덕스 구조를 직접 구현해보게 된다면 상태 관리에 대해 더 잘 이해할 수 있을 것 같아 글을 남기며 정리해본다!

먼저 Redux의 컨셉인 Flux패턴에 대해 간단하게 학습해보자

왜 React는 Flux패턴을 추구하는 걸까?

여기서 조심할 점은 Redux는 자바스크립트의 라이브러리이지 React의 전유물이 아니다..!
리액트에서 사용 시 npm i react-redux로 설치하는 이유도 그것이다.
나는 React를 주로 쓰기 때문에 React의 관점에서 작성하는 글이다.

로직, 데이터, 뷰로 나누어 관리하는 MVC 패턴

기존의 MVC패턴은 하나의 Controller 조작으로 인해 다수의 Model과 View가 복잡하게 연결되는 상황이 발생할 수 있어 깨지기 쉽고 예측 불가능한 코드가 될 가능성이 농후했다. MVC가 무조건 나쁘다는 건 아니고 소규모 애플리케이션에는 적합하지만 애플리케이션이 커질수록 model과 view 간에 의존성이 크다 보니 보니 예측 불가능한 오류들이 여기저기서 튀어나올 수 있다는 것이다.

이 문제를 해결하기 위해 페이스북에서는 MVC와 결별하고 좀 더 예측 가능한 코드를 구조화할 수 있는 Flux 패턴을 고안하게 된다.

 

그래서 Flux패턴은 어떻게 작동할까?

아래 내용은 Flux 공식문서를 번역한 것이므로 오역인 부분이 있을 수도 있다..!

Flux는 MVC패턴을 기피하고 단방향 데이터 흐름을 추구한다. Flux 애플리케이션은 Dispatcher, stores, views(React components)라는 세 개의 큰 파트를 가지고 있는데 리액트에서 상호작용이 일어나면 dispatcher를 통해 데이터의 업데이트에 영향을 받는 곳에 작업을 전파한다. 이러한 흐름은 예측 가능하며 선언적 프로그래밍인 React와 찰떡으로 잘 작동한다. 또한 React의 특징인 가상 DOM, 라이프사이클 등을 보면 View를 독립적으로 다루기 위함을 볼 수 있는데 이는 MVC보다는 Flux가 잘 맞는 것을 확인할 수 있다.

 

모든 데이터는 Dispatcher를 통해 중앙 허브로 흐르게 된다. 

Dispatcher는 MVC의 Controller를 대처하는 역할이며 어떤 Action이 발생했을 때 어떻게 Store를 업데이트할지를 결정한다. Store가 변경되면 View도 동시에 변경된다. 또 선택적으로 Dispatcher가 처리할 Action을 발생시킬 수도 있다. 이처럼 시스템의 컴포넌트 간 데이터 흐름은 단방향으로 유지됨을 알 수 있다. 

-> 데이터는 단방향으로만 흐르고 각각의 Store와 View는 직접적인 영향을 주지 않는다!

 

이제 Flux패턴을 사용하는 Redux의 dispatch, action, store 등의 개념이 어디서 왔는지 알게 된다.

 

Redux 직접 작성해보기

1) store는 subscribe(), dispatch(), getState()를 메서드로 가진 객체이다.

2) reducer는 createStore의 내부 상태인 state와, action 객체를 인자로 받아 action type에 따라 로직을 처리한 후 새로운 state를 리턴하는 함수이다.

-> createStore는 reducer(state를 변경시키는 함수)를 인자로 받아 store(데이터 저장소)를 리턴하는 함수이다.

 

리덕스의 CreateStore.ts 파일이다. 주석 설명이 잘 되어있어 읽어보면 좋다

https://github.com/reduxjs/redux/blob/master/src/createStore.ts

 

GitHub - reduxjs/redux: Predictable state container for JavaScript apps

Predictable state container for JavaScript apps. Contribute to reduxjs/redux development by creating an account on GitHub.

github.com

 

Store

전체 데이터를 관리하는 store이다.

function createStore() {
  let state;
  const getState = () => ({ ...state });
  return { getState };
}

const store = creatStore();

Redux의 컨셉 중 하나인 불변성(immutabilty)을 위해 redux는 state 값을 직접 변경하지 않고 spread 연산자를 이용해 객체를 얕은 복사 하여 새로운 참조를 리턴하게 된다. 

state가 의도치 않게 변경되지 않도록 getState함수로만 state 값을 확인할 수 있다. 여기서 getState는 클로저의 개념이 적용되는데 createStore함수 외부에서는 state를 직접 변경하지 못한다. 

Reducer

리듀서는 앞서 말했듯이 state를 변경시키는 함수이다. 

나는 state === data 라 배웠으나 엄밀히 말하면 state는 변하는 데이터를 뜻한다.

const reducer = () => {};
const store = createStore(reducer)

reducer와 store는 대략 이런 로직으로 구성되어있을 것이다.

const reducer(state = "hello") => {
	return "world";
};

const store = createStore(reducer);
console.log(store.getState());

// world

return에 변경 로직을 넣으면 새로운 상태 값을 반환하게 될 것이다.

하지만 수많은 변경 함수를 reducer return 값에 넣을 수 없을 것이다. 이때 reducer에는 state를 변경하기 위한 action 인자가 필요하다. 

const reducer = (state, action) => {
  if (action.type === "ADD") {
    return state + 1;
  } else {
    return state;
  }
};

 

dispatch

action은 dispatch에 의해 일어난다.

따라서 reducer에 인자로 전달되기 위해서는 dispacth 안에 reducer함수가 호출되는 구조일 것이다. 

function createStore(reducer) {
  let state = "hello";

  const dispatch = (action) => {
    state = reducer(state, action);
  };
  const getState = () => ({ ...state });

  return {
    getState,
    dispatch,
  };
}

const store = creatStore();

이렇게 되면 아주 기본적인 리덕스 구조가 완성되었다.

 

결과

미니 리덕스...

import { createStore } from 'react-redux'

const reducer = (state, action) {
  if(action.type === "ADD"){
    return { ...state, todo: [...state.todos, action.payload]}
  }
  return state
}

const store = createStore(reducer);

이제 각각 어떤 역할을 하는지 이론적인 개념이 뿐만 아니라 어떻게 작동하는지 알게되었다!

 

참고

페이스북의 결정: MVC는 확장에 용이하지 않다. 그렇다면 Flux다.

[React]Redux 직접 만들어보기 

[Redux] 리덕스를 구성하는 5개의 핵심 Action, Reducer, Store, Dispatch, Subscribe