TIL80. Redux&React

조연정·2021년 5월 15일
0
post-thumbnail

자바스크립트 프레임워크인 앵귤러,뷰와 달리 라이브러리인 리액트는 자체적으로 상태관리 도구를 제공하지 않기 때문에 상태관리를 위해서 'Redux'와 사용된다. 리액트에 redux를 적용하여 사용해보자.

구체적인 실습예제 만들어보기

1)컴포넌트 구조


리덕스를 사용할 때 가장 많이 사용하는 패턴은 프레젠테이션 컴포넌트 + 컨테이너 컴포넌트를 분리하는 것이다. 이 패턴을 사용하면 코드의 재사용성이 높아지는 장점이 있다.

  • 프레젠테이셔날 컴포넌트: 단순히 props를 받아와 화면에 ui를 보여주기만 하는 컴포넌트

  • 컨테이너 컴포넌트: 리덕스와 연동되어 있는 컴포넌트로 리덕스로부터 상태를 받아오거나 리덕스 스토어에 액션을 디스패치하기도 한다.

ducks 패턴

가장 일반적인 구조는 actions,constants,reducers기능별로 파일을 하나씩 만드는 방법이다. 하지만 새로운 액션을 만들 때마다 세 종류의 파일을 모두 수정해야하는 불편함이 있다. 그래서 생겨난 것이 액션타입, 액션생성함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 작성하는 ducks 패턴 방식이다.

2-1)간단한 counter 모듈

1. 액션타입 정의
액션 타입은 대문자, 문자열 내용은 '모듈 이름/액션 이름'과 같은 형태로 작성하자. 프로젝트가 커졌을 때 액션의 이름이 충돌하지 않기 위해서 모듈이름을 넣는 것이 좋다.

//couter.js
                   모듈이름/액션이름
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

2. 액션 생성 함수

//couter.js

export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

3. 초기상태 && 리듀서 함수

//couter.js

const initialState = {
  number: 0,
};

function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        number: state.number + 1,
      };
    case DECREASE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
}

2-2)복잡한 todos 모듈(with 파라미터가 있는 액션생성함수)

//todos.js
//액션 타입 정의하기
const CHANGE_INPUT = "todos/CHANGE_INPUT"; //인풋 값을 변경
const INSERT = "todos/INSERT"; // 새로운 todo 등록
const TOGGLE = "todos/TOGGLE"; //todo를 체크 or 체크해제
const REMOVE = "todos/REMOVE"; // todo를 제거

//액션 생성 함수 만들기 (파라미터 필요)
export const changeInput = (input) => ({
  type: CHANGE_INPUT,
  input, //액션 객체 안에 추가 필드로 넣음
});

let id = 3;
export const insert = (text) => ({
  type: INSERT,
  todo: {
    id: id++,
    text,
    done: false,
  },
});
export const toggle = (id) => ({
  type: TOGGLE,
  id,
});

export const remove = (id) => ({
  type: REMOVE,
  id,
});

//초기상태 및 리듀서 함수
const initialState = {
  input: "",
  todos: [
    { id: 1, text: "리덕스 기초 배우기", done: true },
    { id: 2, text: "리액트 + 리덕스", done: false },
  ],
};

function todos(state = initialState, action) {
  switch (action.type) {
    case CHANGE_INPUT:
      return {
        ...state,
        input: action.input,
      };
    case INSERT:
      return {
        ...state,
        todos: state.todos.concat(action.todo),
      };
    case TOGGLE:
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.id ? { ...todo, done: !todo.done } : todo,
        ),
      };
    case REMOVE:
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.id),
      };
    default:
      return state;
  }
}

export default todos;

루트 리듀서

스토어를 만들 때에는 리듀서를 하나만 사용해야 한다.'counter','todos'두 개의 리듀서를 만들었기 때문에 이것들을 하나로 합쳐주는 작업이 필요하다. 함수인 리듀서를 객체처럼 합칠 수 없기 때문에 리덕스에서 제공하는 combineReducers라는 유틸 함수를 사용하면 쉽게 처리할 수 있다.

import { combineReducer } from "redux";
import counter from "./counter";
import todos from "./todos";

const rootReducer = combineReducer({
  counter,
  todos,
});

export default rootReducer;

3)리액트에 리덕스 적용하기

스토어 + Provider 컴포넌트 생성

최상단인 src디렉터리 index.js에서 적용. 스토어 생성 후, provider 컴포넌트로 app컴포넌트를 감싼다.
위에서 생성한 rootReducer를 store에 첫번째 인자로 넘겨줘서 couter, todos 두 개의 리듀서가 컴바이닝되고 스토어로 사용할 수 있게 된다.

* redux Devtools

리덕스 개발자 도구로 리덕스 스토어 내부 상태를 확인할 수 있다.

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

const store = createStore(rootReducer, composeWithDevTools());

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

4)컨테이너 컴포넌트 만들기

리덕스 스토어와 연동된 컨테이너 컴포넌트를 통해 컴포넌트에서 리덕스 스토어에 접근하여 원하는 상태값을 받아오고, 액션도 디스패치할 수 있다.

import React from "react";
import { connect } from "react-redux";
import { increase, decrease } from "../modules/counter"; //액션함수 불러오기
import Counter from "../components/Counter"; // 연결할 ui 코드 부분 가져오기

const CounterContainer = ({ number, increase, decrease }) => {
  return (
    <Counter number={number} onIncrease={increase} onDecrease={decrease} />
  );
};

const mapStateToProps = (state) => ({ number: state.counter.number });
const mapDispatchToProps = (dispatch) => ({
  increase: () => {
    dispatch(increase()); //액션함수 불러오기
  },
  decrease: () => {
    dispatch(decrease());
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterContainer);

1. countContainer 생성

ui코드를 담당하는 Counter를 가져온다.

2-1. mapStateToProps, mapDispatchToProps선언하여 connect 함수 사용하기

컴포넌트와 리덕스를 연결하려면 connect 함수를 사용해야한다. connect함수는 다음과 같이 구성된다.
connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)

  • mapStateToProps: 스토어 안의 state를 컴포넌트의 props로 넘겨주기 위한 함수로, state를 파라미터로 받아온다.
  • mapDispatchToProps: 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위한 함수로, store 내장함수 dispatch를 파라미터로 받아 온다.

2-2. 익명함수 형태로 connect함수 사용하기

mapStateToProps, mapDispatchToProps를 선언하지 않고, connect 함수 내부에 익명 함수 형태로 선언하는 방법도 있다. 코드가 더 깔끔해보이는 효과도 있고, 취향에 따라 선택하면 될 것 같다.

export default connect(
  (state) => ({ number: state.counter.number }),
  (dispatch) => ({
    increase: () => dispatch(increase()),
    decrease: () => dispatch(decrease()),
  }),
);

2-3. mapDispatchToProps 파라미터를 액션생성함수로 넣어주어 사용하기

앞에 방법보다 편한 방법으로 mapDispatchToProps에 해당하는 파라미터를 함수 형태가 아닌 액션 생섬 함수로 이루어진 객체 형태로 넣어주는 것이있다.

export default connect((state) => ({ number: state.counter.number }), {
  increase,
  decrease,
})(CounterContainer);

두 번째 파라미터를 객체 형태로 넣어주면 connect함수가 내부적으로 *bindActionCreators 작업을 대신해준다.
*bindActionCreators:액션생성함수가 많을 경우 일일히 dispatch로 감싸주는 작업이 번거롭게 느껴질 수 있다. 이때 리덕스에서 제공하는bindActionCreators유틸 함수를 사용하면 간단해진다.

import {bindActionCreators} from 'redux';
...생략
dispatch => 
bindActionCreators(
  {액션함수1,액션함수2,...,
 },
 dispatch,
),
)(연동할 컴포넌트)
profile
Lv.1🌷

1개의 댓글

comment-user-thumbnail
2021년 5월 20일

연정님은 정말 꾸준히 노력하시는 것 같아요 ㅎㅎ 오랜만에 왔는데도 계속 이어지는 블로그 포스트 잘 보고 갑니다. 항상 응원합니다 !

답글 달기