프론트엔드 첫걸음

리덕스 관련 코드 작성하기 본문

개발 공부/React

리덕스 관련 코드 작성하기

차정 2022. 9. 4. 14:54
리액트를 다루는 기술 17장

 

기본 리덕스 코드 작성하기

리덕스 코드는 1.액션타입, 2. 액션생성함수, 3.리듀서 코드를 작성해야한다.
코드를 작성하는 방법은 대표적으로 두가지가 있는데, 첫번째는 
constants(액션타입), actions(액션생성함수),reducers(리듀서코드) 폴더 아래 각각의 기능별로 다른 파일을 만들어서 작성하는 방법이다.
두번째는 액션타입, 액션생성함수, 리듀서 코드를 기능별로 파일 하나에 몰아서 다 작성하는 방식이다. 이를 Ducks패턴이라고 한다.

 

- 액션타입 정의하기 

const INCREASE = 'counter/INCREASE'

액션타입은 대문자로 정의하고, 문자열 내용은 '모듈이름/액션이름'과 같은 형태로 작성한다.
문자열 안에 모듈 이름을 넣음으로써 프로젝트가 커졌을 때 액션의 이름이 충돌되지 않도록 한다.

 

 

- 액션생성함수 만들기

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

 

export는 여러개를 내보낼 수 있지만 export default는 단 한개만 내보낼 수 있다.

 

- 초기상태 및 리듀서 함수 만들기 

const initialState = {
  number : 0 
}

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

export default counter;

 

불러오는 방식도 다르다 (default 로 export되지 않은 컴포넌트는 중괄호로 묶어서 가져온다)

import counter, { increase } from './counter';

 

 

 


루트리듀서 만들기

리듀서를 여러 개 만든 경우 리듀서를 하나로 합쳐준다.
createStore함수를 사용하여 스토어를 만들 때 리듀서를 하나만 사용해야하기 때문에 
리덕스에서 제공하는 combineReducer 사용해서 여러개의 리듀서를 하나로 묶어준다.

import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos,    
})

export default rootReducer;


파일 이름을 index.js로 설정하면 import하는 쪽에서 디렉토리 이름까지만 써줘도 된다.

import rootReducer from './modules';

 

 

import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';

import './index.css';
import App from './App';
import rootReducer from './modules';

const store = createStore(rootReducer);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

 

 


컨테이너 컴포넌트

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

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

const makeContainer = connect(mapStateToProps, mapDispatchToProps)

makeContainer(연동할컴포넌트)

- mapStateToProps
  리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기위해 설정하는 함수
  스토어가 업데이트되면 mapStateToProps도 호출된다.
  mapStateToProps의 반환값은 래핑된 컴포넌트의 props에 병합될 plian object여야 한다.
  스토어 업데이트를 구독하고싶지 않으면 mapStateToProps 자리에 null 이나 undefined를 넣는다.

- mapDispatchToProps는 액션생성함수를 컴포넌트의 props로 넘겨주기위해 사용하는 함수
  store의 dispatch를 유일한 매개변수로 받아서 객체를 반환한다.
 객체는 action들을 dispatch한다. 
 스토어가 업데이트되면 mapDispatchToProps도 호출된다.

const mapDispatchToProps = (dispatch) => ({
  //mapDispatchToProps(dispatch).increase는 dispatch(increase())를 return한다. 
  increase: () => dispatch(increase());
  ,
  //그런데 중괄호를 넣어서 dispatch를 리턴안하게해도 동작하는 이유는 무엇인가......
  decrease: () => {
    dispatch(decrease());
  },
});


connect함수 호출시 반환되는 함수에 파라미터로 컴포넌트를 넣어주면, 이 컴포넌트가 리덕스와 연동된다.

 

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

 

위 코드는 아래와 같이 축약해서 쓸 수 있다.

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

 

increase : () => dispatch(increase())
decrease: () => dispatch(decrease()),

이렇게 액션함수를 호출하고 dispatch로 감싸는 작업이 귀찮을 때 bindActionCreators를 사용할 수 있다. 

import { bindActionCreators } from 'redux';

//중략//

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

 

이보다 더 쉬운방법은 mapDispatchToProps자리에 함수 대신 Action creator 목록으로 이뤄진 객체를 넣어주는것이다.
이렇게하면 connect함수가 내부적으로 bindActionCreators 작업을 대신함.

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