React-Redux 코드 작성 단계 요약
1. 필요한 패키지 설치
...
$ npm install redux --save
$ npm install react-redux --save
$ npm install react-actions --save
// option
$ npm install redux-devtools-extension --save
2. reducer 만들기
리듀서를 정의한 파일들은 modules라는 폴더에 모아둡니다.
modules/component.js
import { createAction, handleActions } from 'redux-actions';
사용할 action type을 다음과 같은 형식으로 선언해 줍니다.
예시는 counter 라는 컴포넌트에서 INCREASE와 DECREASE 라는 type의 action을 사용한다는 것을 의미합니다.
const TYPENAME_IN_CAPITALLETTER = 'component_name/TYPENAME_IN_CAPITALLETTER;
// example)
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
react-actions 라이브러리에서 가져온 createAction을 사용하여 액션 생성 함수를 선언합니다.
이 함수는 외부 파일에서 접근해야 하기 때문에 export를 붙여 줍니다.
위에서 선언한 Type 이름을 createAction 함수에 파라미터로 전달하고, 이를 액션 생성 함수로 선언합니다.
export const action_create_function = createAction(TYPE_NAME);
// example)
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
초기 state를 선언합니다. 이는 객체 형태가 아니라 단순 정수나 문자열로도 설정할 수 있습니다.
const initialState = ... ;
// example)
const initialState = {
number: 0
}
그리고 react-actions 라이브러리에서 가져온 handleAction 함수를 활용하여 리듀서 함수를 작성하니다.
이 함수를 두개의 파라미터를 갖습니다.
- 1번재 파라미터: 각 액션에 대한 업데이트 함수
- 2번째 파라미터: 초기 상태
// reducer 함수 이름은 보통 컴포넌트 이름의 소문자 버전으로 설정합니다
const reducer_name = handleActions(
{
[ACTION_TYPE]: update 함수
...
},
initialState
);
// example)
const counter = handleActions(
{
[INCREASE]: state => ({number: state.number +1}),
[DECREASE]: state => ({number: state.number -1}),
},
initialState
);
export default counter;
handleAction을 사용하지 않으면 다음과 같이 switch 문을 사용하면 됩니다. handleAction을 사용하면 가독성이 훨씬 높아지는 것 같죠?
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;
}
}
3. rootReducer 만들기
프로젝트의 규모가 커지다 보면 수많은 컴포넌트와 리듀서가 생성됩니다. 하지만 리덕스의 원칙 중 '단일 스토어' 원칙을 따르기 위해선 하나의 스토어가 하나의 리듀서만을 사용해야 합니다.
그렇게 때문에 기존 여러 개의 리듀서를 하나로 합쳐주는 rootReducer가 필요합니다.
rootReducer 역시 리듀서이기 때문에 modules 폴더에 만들어 줍니다.
modules/index.js
파일 이름을 index로 설정하면 나중에 import 할 때 디렉터리 이름까지만 입력해도 불러올 수 있습니다.
ex) import rootReducer from './modules';
import { combineReducers } from 'redux';
import reducer from './reducer';
const rootReducer = combineReducers({
reducer,
...
});
export default rootReducer;
reducer를 새로 성생할 때마다 이 루트 리듀서의 combineReducer 함수 내부에 추가로 등록해주면 됩니다.
4. store 생성 후 Provider로 전달하기
이제 생성한 reducer를 store에 등록하고 src/index.js에서 생성하여 사용할 수 있게 해야 합니다.
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(rootReducer,composeWithDevTools());
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
);
redux 패키지에서 불러온 createStore 함수를 사용하여 store를 생성합니다.
이 함수는 rootReducer를 파라미터로 받습니다. rootReducer는 위에서 작성한 루트 리듀서를 임포트해온 것입니다.
두번째 파라미터에 전달된 composeWithDevTools()는 위에서 option으로 설치한 구글 extension, Redux DevTools 사용을 위한 것입니다.
생성한 store는 Provider로 프로젝트 전역에서 사용할 수 있도록 전달합니다.
5. Presentational Component 만들기
presentational component는 props를 받아와서 화면에 ui를 보여주기만 하는 컴포넌트입니다.
프레젠테이셔널 컴포넌트들은 components라는 디렉토리에 저장해줍니다.
필요한 props 들을 받아와서 개발자가 원하는대로 ui를 디자인한 코드를 작성하면 됩니다.
// example)
import React from 'react';
const Counter = ({ number, onIncrease, onDecrease }) => {
return(
<div>
<h1>{number}</h1>
<div>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
</div>
);
}
export default Counter;
6. Container Component 만들기
컨테이너 컴포넌트는 리덕스와 연동되어 있는 컴포넌트로 리덕스로부터 상태를 받아오기도 하고, 스토에어 액션을 디스패치하기도 합니다.
컨테이너 컴포넌트들은 containers라는 디렉토리에 저장합니다.
프레젠테이셔널 컴포넌트에 스토어에서 디스패치해 온 값들을 props로 넘겨주면 호출하고,
실제 App.js 파일에서 호출되는 컴포넌트는 컨테이너 컴포넌트입니다.
connect 함수를 사용할 수도 있고, useSelector와 useDispatch hooks를 사용할 수도 있습니다.
- connect 함수 사용하기
import React from 'react';
import { connect } from 'react-redux';
import PresentationlComponent from '../components/PresentationlComponent';
import { action_create_function, ... } from '../modules/component_reducer';
const ContainerComponent = ({ props ... }) => {
return <PresentaionalComponent props={props} ... />;
};
export default connect(
state => ({
var1: state.component.var1,
...
}),
{
action_create_function,
...
}
)(ContainerComponent);
connect 함수는 mapStateToProps 와 mapDispatchToProps 함수를 사용하는데 mapStateToProps 와 mapDispatchToProps를 위에서 따로 선언해주고 전달하기도 하지만 저는 익명 함수로 전달하는게 더 편해서 이렇게 합니다.
- Hooks 사용하기
import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import PresentationlComponent from '../components/PresentationlComponent';
import { action_create_function, ... } from '../modules/component_reducer';
const ContainerComponent = () => {
const s1 = useSelector(state => state.action_create_function.s1);
const dispatch = useDispatch();
const propsFunction = useCallback(() => dispatch(action_create_function(), [dispatch]));
...
return <PresentationalComponent s1={s1} propsFunction={propsFunction} ... />;
};
export default ContainerComponent;
useSelector hook을 사용하여 리듀서에 정의된 상태를 가져오고, action에 등록된 업데이트 함수를 dispatch로 가져옵니다.
그리고 리덕스에서 가져온 props들을 프레젠테이셔널 컴포넌트에 전달하며 호출합니다.
액션을 디스패치할 때 useCallback을 사용하면 성능 최적화에 도움이 됩니다.
// example)
import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';
const CounterContainer = () => {
const number = useSelector(state => state.counter.number);
const dispatch = useDispatch();
const onIncrease = useCallback(() => dispatch(increase(), [dispatch]));
const onDecrease = useCallback(() => dispatch(decrease(), [dispatch]));
return <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease}/>;
};
export default CounterContainer;
7. 최상위 컴포넌트에서 Container Component 호출하기
리액트 프로젝트에서 최상위 컴포넌트인 src/App.js 파일에서 Container Component 들을 임포트하고 사용합니다.
importimport './App.css';
import ContainerComponent from './containers/ContainerComponent';
const App = () => {
return (
<div>
<ContainerComponent />
</div>
);
}
export default App;
redux 플로우는 다양한 형태로 존재하기 때문에 개발자마다 자신이 편한 방식을 선택해서 사용하면 됩니다.
저는 각 단계에서 사용할 라이브러리 등이 위에서 소개한 내용이 편하다고 생각해서 이렇게 사용할 예정입니다.
References
- 리액트를 다루는 기술 (김민준)
'웹 개발 > React' 카테고리의 다른 글
React) react-router-dom 특정 경로에서만 특정 컴포넌트 랜더링 되게 하기 (0) | 2021.04.07 |
---|---|
React) fullpage.js처럼 CSS scroll-snap 속성으로 스크롤 조작 완료 시 특정 위치/오프셋 설정하기 (0) | 2021.04.07 |
React) Redux DevTools 사용하기 (0) | 2021.02.22 |
React) react-router-dom : NavLink 로 활성화된 버튼 active style 적용하기 (0) | 2021.02.18 |
React) 불변성 (0) | 2021.02.17 |