코딩/Redux

[Redux]리덕스 -Today I'm Learned (3) 리덕스의 구성

카슈밀 2021. 2. 2. 21:42
반응형

해당 코드는 '도서 "리액트를 다루는 기술"에 저술된 코드입니다.' 

import React, { useReducer } from "react";

const initialState = () => {
	counter: 1
};

function reducer(state = initialState, action) {
  // action.type에 따라 다른 작업 수행
  switch (action.type) {
    case "INCREMENT":
      return { value: state.value + 1 };
    case "DECREMENT":
      return { value: state.value - 1 };
    default:
      return state;
  }
}
  • useReducer
    리덕스를 이해하기 위해서 useReducer를 이해해야한다.
    그 이유로는 리듀서에는 파라미터를 2개 받는데, (state, action)을 받는다.
    해당 state를 통해서 state의 초기값을 설정하고, action을 통해서 해당 state의 값을 변화한다.
    이 부분이 해당 dispatch에서 리덕스가 타임머신을 구동하는데, 사용된다.
  • store
    한개의 프로젝트에선 한개의 'store'를 구성해야한다. 여러 개도 작성하여도 되나, 데이터 관리측면에서
    무조건 한개의 'store'로 구성하는 것이 데이터를 관리하기가 좋으므로 이를 꼭 유지해야한다.
  • dispatch
    스토어에 내장된 함수이다. "액션을 발생시킨다." 역할.
    dispatch(action)과 같은 형태로 액션객체를 파라미터로 넣어서 호출한다.
    디스패치가 호출되면 스토어는 Reducer 함수를 실행시켜 새로운 상태를 만들어준다.
  • subscribe
    subscribe도 store의 내장 함수이다.
    subscribe 함수 안에 리스너를 집어넣어 호출해주면, 리스너 함수가 액션이 dispatch되어 상태가 업데이트 될때마다 호출되게 된다.
    const listener = () => {
    	console.log('update complete')
    	}
    const unsubscribe = store.subscribe(listener);
    unsubscribe(); // 추후 구독을 비활성화할 때 함수를 호출
  • action
    상태에 어떠한 변화가 필요하면 action이란 것이 발생한다.
    이는 이러한 형식으로 이루어 져 있다.
    액션 객체는 반드시 "type" 필드를 가지고 있어야한다.
{
  type: 'INCREASE'
}
  • action 생성함수
    액션 생성 함수는 액션 객체를 만들어주는 함수.
    어떤 변화를 일으켜야 할 때마다 액션객체를 만들어야 하는데,
    이러한 액션 객체를 매번 작성하기가 번거로울 수 있고,
    만드는 과정에서 실수로 정보를 놓칠 수도 있으므로 이러한 일을 방지하기 위해서 action 함수를 만들어 관리한다.
function addTodo(data) {
  return{
    	type: "ADD_TODO",
        data
  };
};

 

리덕스의 구조는 일반적으로

'actions, constants, reducers라는 세 개의 디렉터리를 만들고 그 안에 기능별로 파일을 하나씩 만드는 방식.

코드를 종류에 따라 다른 파일에 작성하여 정리할 수 있어서 편리하지만,

새로운 액션을 만들 때마다 세 종류의 파일을 모두 수정해야 하기 때문에 불편하기도 합니다.'

이러한 이유로 Ducks 타입이 나왔다.

 

Ducks 타입이란?

'액션 타입, 액션 생성 함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 다 작성하는 방식입니다.

이러한 방식을 Ducks 패턴이라고 부르며,

앞서 설명한 일반적인 구조로 리덕스를 사용하다가 불편함을 느낀 개발자들이 자주 사용합니다.'

 

루트리듀서(rootReducer)를 쓰는 이유는?

 'createStore 함수를 사용하여 스토어를 만들 때는 리듀서를 하나만 사용해야 합니다.

그렇기 때문에 기존에 만들었던 리듀서를 하나로 합쳐 주어야 하는데요.

이 작업은 리덕스에서 제공하는 combineReducers라는 유틸 함수를 사용하면 쉽게 처리할 수 있습니다.'

 

컨테이너 컴포넌트란?

리덕스 스토어와 연동된 컴포넌트를 컨테이너 컴포넌트라고 한다.

'해당 컴포넌트를 리덕스와 연동하려면 react-redux에서 제공하는 connect 함수를 사용해야한다.

connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)

mapStateToProps는 리덕스 스토어 안의 상태(state)를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수이고, 

mapDispatchToProps액션 생성 함수(dispatch)를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수입니다.'

- connect 부분이 굉장히 어렵네요 - 

컨테이너에서 연결은 4가지 방식으로 구현이 됩니다.

import React from ‘react‘;
import { connect } from ‘react-redux‘;
import Counter from ‘../components/Counter‘;
import { increase, decrease } from ‘../modules/counter‘;


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);

mapStateToProps, mapDispatchToProps를 제거하고, state, dispatch로 변경하여 훨씬 가독성있게 작성된 모습입니다.

import React from ‘react‘;
import { connect } from ‘react-redux‘;
import Counter from ‘../components/Counter‘;
import { increase, decrease } from ‘../modules/counter‘;


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



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

액션 생성 함수를 호출하여 디스패치하는 코드가 한 줄이기 때문에 불필요한 코드 블록을 생략해 주었습니다.
다음 두 줄의 코드는 작동 방식이 완전히 같습니다.

 

'컴포넌트에서 액션을 디스패치하기 위해 각 액션 생성 함수를 호출하고 

dispatch로 감싸는 작업이 조금 번거로울 수도 있습니다.

특히 액션 생성 함수의 개수가 많아진다면 더더욱 그럴 것입니다.

이와 같은 경우에는 리덕스에서 제공하는 bindActionCreators 유틸 함수를 사용하면 간편합니다.'

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';

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

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

이보다 쉬운 방법이 있는데, dispatch부분의 코드를 함수가 아닌 객체로 넣는 방식이 있습니다.

import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';

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

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

'위와 같이 두 번째 파라미터를 아예 객체 형태로 넣어 주면 

connect 함수가 내부적으로 bindActionCreators 작업을 대신해 줍니다.'

 

해당 코드는 '도서 "리액트를 다루는 기술"에 저술된 코드입니다.' 

 

 

리덕스의 절대 규칙

  1.  1개의 스토어
  2.  순수함수의 상태
  3.  읽기 전용 상태(state 값에 직접 접근하면 안된다.)
728x90