ivory's Log
그게 무엇이라도 항상 쉬운 일이다.

ivory's DevLog

Redux의 흐름과 예제

ivorycode 2021. 9. 28. 13:34
반응형

Redux의 3가지 원칙

1. 하나의 애플리케이션 안에는 하나의 스토어만 사용하자.

특정 업데이트가 빈번하게 일어나거나, 애플리케이션 특정 부분을 분리시키게 되면, 여러 개의 스토어를 사용할 수 있다, 하지만, Redux는 하나의 애플리케이션에서 여러 개의 스토어 사용을 권장하지 않는 이유는 개발 도구를 활용하지 못하게 되어 디버깅이 어려워지기 때문이다. 그러니 하나의 스토어만 사용하여 디버깅을 용이하게 하고, 서버와의 직렬화를 통해 클라이언트에서 데이터를 쉽게 받아올 수 있도록 한다.

 

2. state는 읽기 전용이다.

Redux는 state를 변경할 때, 기존 값은 건드리지 않고, action을 일으켜 새로운 state를 생성하여 업데이트해주는 방식이다. 이것은 Redux 고유의 불변성을 지키고, state를 변경하려는 의도를 파악하고 디버깅을 용이하게 한다.

 

3. Reducer는 순수 함수여야 한다.

변화를 일으키는 Reducer는 반드시 순수 함수여야 한다. 순수 함수가 다음 조건을 만족해야 한다.

  • Reducer 함수는 이전 state와 action 객체를 parameter로 받는다.
  • parameter외의 값에 의존해선 안된다.
  • 이전 state는 건드리지 않고, 변화를 준 새로운 state 객체를 만들어서 리턴한다.
  • 같은 parameter로 호출된 Reducer 함수는 언제나 같은 결과를 리턴해야 한다.

 

Redux Flow

지금부터 아래의 이미지와 함께 Redux의 전체 흐름에 대해 정리해보자.

Redux Flow 이미지 출처: www.google.com

초기 상태
- store에서 reducer를 호출하고 리턴 값을 초기로 상태로 저장한다.
- UI가 최초 렌더링 될 때, UI 컴포넌트는 store의 state에 접근하여 렌더링에 활용한다. 그리고 그 state가 업데이트되는 것을 subscribe 한다.

 

Flow(업데이트) 순서

  1. Deposit $10 버튼을 클릭한다.
  2. dispatch 함수를 실행시켜 action을 일으킨다.
  3. store에선 이전 state와 현재의 action으로 reducer함수를 실행하고, 리턴된 값을 새로운 state로 저장한다.
  4. store에서 store를 subscribe하고 있던 UI 컴포넌트들 에게 업데이트 여부를 알린다.
  5. store의 데이터가 필요한 각각의 컴포넌트들은 state가 업데이트되었는지 확인한다.
  6. 데이터가 변경된 요소들은 새로운 데이터로 강제 리렌더링 되므로화면에 표시되는 내용을 업데이트할 수 있다.

 

Counter 예제 만들어보기

카운터 예제를 통해 Redux를 직접 활용해보도록 하자!! 참고로 React를 활용하여 Redux를 적용해볼 계획이니, 이점 반드시 참고하길 바랍니다!!

# 프로젝트 생성

# react 프로젝트 생성
npx create-react-app redux_counter

 

# 리덕스 설치하기

# redux 설치하기
# NPM
npm install redux

# Yarn
yarn add redux

# react-redux 설치하기
# react에서 redux를 사용하려면 redux와 react-redux 모두 설치해줘야 한다.

# NPM
npm install react-redux

# Yarn
yarn add react-redux

 

# 폴더 구조

Ducks 패턴을 적용한 예제

예전엔 src 폴더에 store(또는 module)라는 디렉토리를 생성해서 그 안에 action, reducer, type이라는 디렉토리를 생성하고 각각에 맞는 Redux파일을 작성했었다. 그러나 하나의 기능을 수정하려고 하면, 해당 기능과 관련된 여러 개의 파일을 수정해야 하는 일이 발생하는데, 이러한 불편함을 개선하고자 나온 것이 Ducks 패턴이다. Ducks 패턴은 구조 중심이 아니라 기능중심으로 파일을 나누기 때문에 단일 기능을 작성할 때나 기능을 수정할 때 하나의 파일만 다루면 되므로 직관적인 코드 작성이 가능하다. 폴더구조는 개인 또는 프로젝트마다 관리하는 방법이 다르므로 정답은 없다. 그 외 나머지는 CRA 폴더 구조와 동일하다.

 

1. action type, action creator 생성

src/store/modules/counter.js

// Actions
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";

// Action Creator
export const increment = () => {
  return {
    type: INCREMENT,
  };
}
export const decrement = () => {
  return {
    type: DECREMENT,
  };
}

 

1. 카운터에 필요한 +, - 2가지 액션을 만들어 준다. 이때, 타입명은 중복이 되지 않도록 작성한다.

2. 다음은 액션 생성 함수를 만들어 주면 되는데, 이때 액션 생성 함수는 나중에 컴포넌트에서 사용될 함수이므로 export 키워드를 반드시 붙여준다.


🆘  액션(action)

state에 변화를 일으킬 때 참조하는 객체

 

2. reducer 생성

src/store/modules/counter.js

// 초기값 설정
const initialState = {
  number: 0,
};

// counterReducer
export default function counter(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        number: state.number + 1,
      };
    case DECREMENT:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
}

 

1. 카운터에 적용될 초기값을 설정해준다. 보통 initialState라고 선언한다.

2. 리듀서를 만들어준다. parameter에 초기 state를 넣어주고, 액션 객체를 넣어준다. 그리고 switch문으로 액션이 발생했을 때의 값을 리턴하도록 작성한다. 어떠한 액션도 발생하지 않았을 때를 대비하여 default도 반환해준다.
(리듀서 앞에 export 키워를 붙여서 선언한 것을 확인할 수 있는데, 그 이유는 아래에서 설명하도록 하겠다.)


🆘  리듀서(reducer)

state에 변화를 일으키는 함수. 리듀서는 parameter를 두 개 받는다. 첫 번째는 현재 state(초기값), 두 번째는 액션 객체를 받는다.

 

3. combineReducer 생성

Redux 내장함수엔 combineReducer라는 기능이 있다. 단어 그대로 모든 리듀서들을 합치는 기능이다. 지금은 비록 카운터 기능 하나뿐이라 리듀서를 합칠 필요가 없지만, 나중에 프로젝트를 진행하다 보면 여러 가지 리듀서가 생성될 것이고, 이것을 하나하나씩 연결한다는 것은 비효율적이므로 알아두면 도움이 많이 되고 복잡하지 않으니 바로 다뤄보자!!

 

src/store/modules/index.js

import { combineReducers } from "redux";
import counter from "./counter";

// import한 리듀서 이름을 그대로 사용하는 경우
export default combineReducers({
  counter,
});

// 리듀서 이름을 지정하는 경우
export default combineReducers({
  // 리듀서 이름: import한 리듀서
  counterData: counter,
});

1. combineReducers와 위에서 export했던 counter 리듀서를 import 해준다.

2. 리듀서를 합쳐주는 방법은 리듀서 이름을 그대로 사용하는 경우, 직접 지정하는 경우 이렇게 2가지가 있다.

 

4. Counter Component 생성

1, 2, 3 과정을 통해 Redux 파일 구조를 생성해봤다. 그런데 파일을 활용할 곳이 없으니 간단하게 컴포넌트를 만들어 View화면을 만들어주도록 하자.

 

src/components/Counter.js

import { useDispatch, useSelector } from "react-redux";
import { decrement, increment } from "../store/modules/counter";

export default function Counter() {
  const dispatch = useDispatch();

  // import한 리듀서 이름을 그대로 사용하는 경우
  const count = useSelector((state) => state.counter.number);

  // 리듀서 이름을 지정하는 경우
  // const count = useSelector((state) => state.counterData.number);

  return (
    <div>
      <h1>COUNTER</h1>
      <h4>{count}</h4>
      <br />
      <button onClick={() => dispatch(increment())}> + </button>
      <button onClick={() => dispatch(decrement())}> - </button>
    </div>
  );
}

1. useDispatch 는 리덕스 스토어의 dispatch를 함수에서 사용할 수 있게 해주는 react-redux에서 제공하는 hook이다. 이것을 이용하여 각각의 액션들을 dispatch 해준다.

2. useSelector는 리덕스 스토어의 state를 조회하는 hook이다. 위의 코드를 잠시 살펴보면 state.counter.number에서 counter는 방금 전 생성한 리듀서의 이름을 말한다.

 

🆘  디스패치(dispatch)

dispatch 함수는 액션 객체를 넘겨줘서 state를 변경할 수 있는 함수다.

 

5. Component 연결 

src/App.js

import logo from "./logo.svg";
import "./App.css";
import Counter from "./components/Counter";

function App() {
  return (
    <div className="App">
      <div className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>Study Redux</p>
        <Counter />
      </div>
    </div>
  );
}

export default App;

1. App.js 파일에 카운터 컴포넌트를 import 해준다.

 

6. Store 생성

src/index.js

하나의 애플리케이션엔 하나의 스토어가 존재한다. 스토어 안에는 state를 담고 있고, 업데이트될 때마다 다시 실행하게 한다.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { createStore } from "redux";
import { Provider } from "react-redux";
import rootReducer from "./store/modules";

const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

1. createStore를 생성하여 위의 과정에서 만든 리듀서를 parameter에 넣어준다. 만들어둔 combineReducer가 있으니 그것을 활용해보자.

2. Provider는 react-redux에서 제공하는 컴포넌트로 리액트 프로젝트에 store를 쉽게 연동할 수 있도록 하는 컴포넌트다. Provider를 불러온 후, props에 아까 선언했던 store를 넣어주자.

 

# 프로젝트 실행

위의 과정을 모두 진행했으니 이제 프로젝트를 실행시켜서 확인해보자!!

# NPM
npm start

# YARN
yarn start

 

Success!!

위의 이미지과 같게 나온다면 성공!!!

 

GitHub 예제 코드
- GitHub에 예제 코드를 공유하였습니다!! 같이 공부하고 공유하는 문화를 만들어 보고 싶습니다!!
- https://github.com/ivory-code/reduxExample.git
반응형