๋ฆฌ๋์ค๋ฅผ ์ฌ์ฉํ๊ธฐ์ํด ์ค์นํด์ผํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค๊ณผ ๋ฆฌ๋์ค์์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ค์ ์์๋ณด์
๐ ์์ ํ๊ฒฝ ์ค์
๋ฆฌ์กํธ ํ๋ก์ ํธ๋ฅผ ์์ฑํ์ฌ ๊ทธ ๋๋ ํ ๋ฆฌ๋ก ์ด๋ํ ์ดํ
$ yarn add redux react-redux
yarn ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ฌ ๋ฆฌ๋์ค์ react-redux ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๋ค
๐จ ๋์์ธํจํด
๋ฆฌ์กํธ ํ๋ก์ ํธ์์ ๋ฆฌ๋์ค๋ฅผ ์ฌ์ฉํ ๋ ๋ง์ด ์ฌ์ฉํ๋ ํจํด์ Presentational and Container Component Pattern ์ด๋ค
Container ์ปดํฌ๋ํธ๋ ๋ฆฌ๋์ค์ ์ฐ๋ํ์ฌ ๋ฆฌ๋์ค๋ก๋ถํฐ ์ํ๋ฅผ ๋ฐ์ ์ค๊ธฐ๋ ํ๊ณ ์คํ ์ด์ ์ก์ ์ ๋์คํจ์น ํ๊ธฐ๋
ํ๋ค
Presentational ์ปดํฌ๋ํธ๋ ์ํ ๊ด๋ฆฌ๊ฐ ์ด๋ฃจ์ด์ง์ง ์๊ณ , ๊ทธ์ props๋ฅผ ๋ฐ์์์ ํ๋ฉด์ UI๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ๋ง ํ๋
์ปดํฌ๋ํธ์ด๋ค
๋์์ธ ํจํด์ ๋ํ ์์ธํ ๋ด์ฉ์ ๋ฐ๋ก ๊ฒ์๊ธ ์์ฑํ๋ฉฐ ์์๋ณด์
๐ป ๋ฆฌ๋์ค ์ฝ๋ ์์ฑํ๊ธฐ
์ก์ ํ์ , ์ก์ ์์ฑ ํจ์, ๋ฆฌ๋์ ํจ์๋ฅผ ๊ธฐ๋ฅ๋ณ๋ก ํ์ผ ํ๋์ ๋ชฐ์์ ๋ค ์์ฑํ๋ ์ด ๋ฐฉ์์ Ducks ํจํด์ด๋ผ ๋ถ๋ฅธ๋ค.
์ก์ ํ์ ์ ์
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
์ก์ ํ์ ์ ๋๋ฌธ์๋ก ์ ์ํ๋ค. ๋ฌธ์์ด์ ๋ด์ฉ์ '๋ชจ๋ ์ด๋ฆ/์ก์ ์ด๋ฆ'์ ํํ๋ก ์์ฑํ๋ค !
๋ฌธ์์ด ์์ ๋ชจ๋ ์ด๋ฆ์ ๋ฃ์์ผ๋ก์จ, ํ๋ก์ ํธ๊ฐ ์ปค์ก์ ๋ ์ก์ ์ ์ด๋ฆ์ด ์ถฉ๋๋์ง ์๊ฒ ๋ฐฉ์งํด์ค๋ค
์ก์ ์์ฑ ํจ์ ๋ง๋ค๊ธฐ
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
export ํค์๋๊ฐ ๋ค์ด๊ฐ๋ ์ด์ ๋ ๋ค๋ฅธ ํ์ผ์์ ๋ถ๋ฌ์ ์ฌ์ฉํ ์ ์๋๋กํ๊ธฐ ์ํจ์ด๋ค
initialState ์์ฑํ๊ธฐ
const initialState = {
number: 0,
};
initialState๋ฅผ ์์ฑํ์ฌ ๋ฆฌ๋์์์ ์ด๋ฅผ ๋ถ๋ฌ์์ ์ด๊ธฐ ์ํ๋ก ์ฌ์ฉํ ์ ์๋๋ก ํ๋ค
๋ฆฌ๋์ ์์ฑํ๊ธฐ
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;
}
}
export default counter;
state๋ initialState๋ฅผ ๋ฃ์ด ์ฃผ์ด์ number ๊ฐ์ ์ค์ ํด ์ฃผ์๊ณ , ๋ฆฌ๋์ ํจ์์๋ ํ์ฌ ์ํ๋ฅผ ์ฐธ์กฐํ์ฌ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ์ฝ๋๋ฅผ ์์ฑํด์ฃผ์๋ค. ๋ง์ง๋ง์ผ๋ก export default ํค์๋๋ก ํจ์๋ฅผ ๋ด๋ณด๋ด ์ฃผ์๋ค.
๋ฃจํธ ๋ฆฌ๋์ ๋ง๋ค๊ธฐ
module ๋๋ ํ ๋ฆฌ ์์ index.js ํ์ผ์ ๋ง๋ ํ์ ์๋์ ๊ฐ์ด ์์ฑํ๋ค
import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';
const rootReducer = combineReducers({
counter,
todos,
});
export default rootReducer;
๋ฆฌ๋์๊ฐ 2๊ฐ ์ด์์ผ ๊ฒฝ์ฐ์ combineReducer๋ผ๋ ์ ํธ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ rootReducer๋ก ๋ง๋ค์ด ์ค๋ค
๋์ค์ ๋ถ๋ฌ์ฌ ๋๋
import rootReducer from '/modules';
์ด๋ฐ ์์ผ๋ก importํ์ฌ ๋ถ๋ฌ์ฌ ์ ์๋ค
์คํ ์ด ๋ง๋ค๊ธฐ & Provider ์ปดํฌ๋ํธ ์ฌ์ฉํ์ฌ ๋ฆฌ๋์ค ์ ์ฉํ๊ธฐ
์ฐ์ createStore๋ฅผ ์ฌ์ฉํ์ฌ rootReducer๋ฅผ ๋ฆฌ๋์๋ก ์ฌ์ฉํจ์ ์๋ ค์ฃผ๊ณ ์คํ ์ด๋ฅผ ์์ฑํ๋ค
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import './index.css';
import App from './App';
import rootReducer from './modules';
import { Provider } from 'react-redux';
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
document.getElementById('root')
);
๋ฆฌ์กํธ ์ปดํฌ๋ํธ์์ ์คํ ์ด๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก App ์ปดํฌ๋ํธ๋ฅผ react-redux์์ ์ ๊ณตํ๋ Provider ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ ์ค๋ค. ์ด ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ๋๋ store๋ฅผ props๋ก ์ ๋ฌํด ์ฃผ์ด์ผ ํ๋ค
Redux DevTools์ ์ค์น ๋ฐ ์ ์ฉ
Redux DevTools๋ ๋ฆฌ๋์ค ๊ฐ๋ฐ์ ๋๊ตฌ์ด๋ฉฐ, ํฌ๋กฌ ํ์ฅ ํ๋ก๊ทธ๋จ์ผ๋ก ์ค์นํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
๋ฆฌ๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐํ ๋ ์ํ๊ด๋ฆฌ๋ฅผ ๋์ฑ ํธํ๊ฒ ๋์์ค๋ค.
const store = createStore(
rootReducer,
window.devToolsExtension && window.devToolsExtension()
);
ํ์ฅ ํ๋ก๊ทธ๋จ์ ์ค์นํ๋ฉด ํธํ์ง๋ง ์ด๋ค ์ค๋ฅ์์์ธ์ง ์๋์ ํ์ง ์์ ์์๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค..!
Connect ์ฌ์ฉํ๊ธฐ
CounterContainers ์ปดํฌ๋ํธ
import React from 'react';
import Counter from '../components/Counter';
const CounterContainer = () => {
return (
<Counter />
);
};
export default CounterContainer;
์ ์ปดํฌ๋ํธ๋ฅผ ๋ฆฌ๋์ค์ ์ฐ๋ํ๊ธฐ์ํด react-redux์์ ์ ๊ณตํ๋ connect ํจ์๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค
connect(mapStateToProps, mapDispatchToProps)(์ฐ๋ํ ์ปดํฌ๋ํธ)
mapStateToProps๋ ๋ฆฌ๋์ค ์คํ ์ด ์์ ์ํ๋ฅผ ์ปดํฌ๋ํธ์ props๋ก ๋๊ฒจ์ฃผ๊ธฐ ์ํด ์ฌ์ฉํ๋ค
mapDispatchToProps๋ ์ก์ ์์ฑ ํจ์๋ฅผ ์ปดํฌ๋ํธ์ props๋ก ๋๊ฒจ์ฃผ๊ธฐ ์ํด ์ฌ์ฉํ๋ค
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(dispatch());
},
});
export default connect(
mapStateToProps,
mapDispatchToProps,(CounterContainer);
)
mapStateToProps๋ ์คํ ์ด์ ์ํ๋ฅผ Props๋ก ๋๊ฒจ์ฃผ๊ณ mapDispatchToProps๋ store์ ๋ด์ฅ ํจ์ dispatch๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ Props๋ก ๋๊ฒจ์ค๋ค
connect ํจ์๋ฅผ ์ต๋ช ํจ์๋ก ๊น๋ํ๊ฒ ์ฌ์ฉํ๊ธฐ
connect ํจ์ ๋ด๋ถ์ ์ต๋ช ํจ์๋ก ์ ์ธํด์ ์ฝ๋๋ฅผ ๊น๋ํ๊ฒ ๋ง๋ค์ด ๋ณด์
const CounterContainer = ({number, increase, decrease}) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
const mapStateToProps = state => ({
number: state.counter.number,
});
export default connect(
state => ({
number: state.counter.number,
}),
dispatch => ({
increase: () => dispatch(increase()),
decrease: () => dispatch(decrease()),
}),
)(CounterContainer);
์ต๋ช ํจ์๋ก ์ ์ธํด์ ๋ ๊น๋ํด์ง ์ฝ๋๋ฅผ ๋ณผ ์ ์๋ค.
bindActionCreators๋ฅผ ์ฌ์ฉํ์ฌ dispatch ๊ฐํธํ๊ฒ ์ฌ์ฉํ๊ธฐ
์ปดํฌ๋ํธ์์ ์ก์ ์์ฑ ํจ์๋ฅผ ํธ์ถํ์ฌ ๋์คํจ์นํ๊ธฐ ์ํด ๊ฐ ์ก์ ์์ฑ ํจ์๋ฅผ ํธ์ถํ๊ณ dispatch๋ก ๊ฐ์ธ๋ ์์ ์ด ๋ฒ๊ฑฐ๋ก์ธ ์ ์๋ค ํนํ ์ก์ ์์ฌ ํจ์์ ๊ฐ์๊ฐ ๋ง์ ์ง๋ค๋ฉด ๋๋์ฑ ๋ฒ๊ฑฐ๋ก์ ์ง๋ค ์ด๋ฅผ ๋ ํธํ๊ฒ ํ๊ธฐ์ํ์ฌ
๋ฆฌ๋์ค์์ ์ ๊ณตํ๋ bindActionCreators ์ ํธ ํจ์๋ฅผ ์ด์ฉํด๋ณด์
import React, { useCallback } 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} />
);
};
const mapStateToProps = state => ({
number: state.counter.number,
});
export default connect(
state => ({
number: state.counter.number,
}),
dispatch =>
bindActionCreators(
{
increase,
decrease,
},
dispatch
)
)(CounterContainer);
์ ์ฒ๋ผ ํ๋ฉด bindActionCreators ํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ง๋ง ๋ ๊ฐํธํ๊ฒ ์ฌ์ฉ ํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ค.
mapDispatchToProps์ ํด๋น ํ๋ผ๋ฏธํฐ๋ฅผ ํจ์ ํํ๊ฐ ์๋ ์ก์ ์์ฑ ํจ์๋ก ์ด๋ฃจ์ด์ง ๊ฐ์ฒด ํํ๋ก ๋ฃ์ด์ฃผ๋ฉด ๋๋ค .
์ด ๊ฒฝ์ฐ์๋ bindActionCreators๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค.
import React, { useCallback } 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,
});
export default connect(
state => ({
number: state.counter.number,
}),
{
increase,
decrease,
},
)(CounterContainer);
์์ ๊ฐ์ด ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ์ฒด ํํ๋ก ๋ฃ์ด์ฃผ๋ฉด connect ํจ์๊ฐ ๋ด๋ถ์ ์ผ๋ก bindActionCreators ์์ ์ ๋์ ํ๋ค
โจ ๋ฆฌ๋์ค ๋ ํธํ๊ฒ ์ฌ์ฉํ๊ธฐ
redux-actions
redux-actions๋ฅผ ์ฌ์ฉํ๋ฉด ์ก์ ์์ฑ ํจ์๋ฅผ ๋์ฑ ๊ฐ๊ฒฐํ๊ฒ ์์ฑํ ์ ์๋ค.
๋ ๋ฆฌ๋์ ํจ์๋ฅผ ์์ฑํ ๋ switch๋ฌธ์ด ์๋ handleActions๋ผ๋ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ์ก์ ๋ง๋ค ์ ๋ฐ์ดํธ ํจ์๋ฅผ ์ค์ ํ๋ ํ์์ผ๋ก ์์ฑ ํ ์ ์๋ค.
$ yarn add redux-actions
CreateAction
counter.js
import { createAction, handleActions } from 'redux-actions';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
์์ ๊ฐ์ด createAction์ ์ฌ์ฉํ๋ฉด ๋ฒ๊ฑฐ๋กญ๊ฒ ๋งค๋ฒ ๊ฐ์ฒด๋ฅผ ์์ฑํ ํ์ ์์ด ๊ฐ๋จํ๊ฒ ์ก์ ์์ฑ ํจ์๋ฅผ ์ ์ธํ ์ ์๋ค.
handleActions
๋ฆฌ๋์ ํจ์์ ๊ฐ๋ ์ฑ์ ๋๊ฒํ๊ธฐ ์ํด handleActions๋ผ๋ ํจ์๋ฅผ ์ฌ์ฉํด๋ณด์
import { createAction, handleActions } from 'redux-actions';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
const initialState = {
number: 0,
};
const counter = handleActions(
{
[INCREASE]: (state, action) => ({ number: state.number + 1 }),
[DECREASE]: (state, action) => ({ number: state.number - 1 }),
},
initialState
);
export default counter;
handleActions ํจ์์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ๊ฐ ์ก์ ์ ๋ํ ์ ๋ฐ์ดํธ ํจ์๋ฅผ ๋ฃ์ด์ฃผ๊ณ ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ์ด๊ธฐ ์ํ๋ฅผ ๋ฃ์ด ์ค๋ค
์ก์ ์์ฑ ํจ์์์ ํ๋ผ๋ฏธํฐ๋ฅผ ํ์๋ก ํ๋ ๊ฒฝ์ฐ createAction ์ฌ์ฉํ๊ธฐ
createAction์ผ๋ก ์ก์ ์ ๋ง๋ค๋ฉด ์ก์ ์ ํ์ํ ์ถ๊ฐ ๋ฐ์ดํฐ๋ payload๋ผ๋ ์ด๋ฆ์ ์ฌ์ฉํ๋ค
์ )
const MY_ACTION = 'sample/MY_ACTION';
const myAction = createAction(MY_ACTION, text => `${text}`;
const actopn = myAction('hello world!');
/*
๊ฒฐ๊ณผ :
{ type: MY_ACTION, payload" 'hello world!' }
*/
์ด์ ํ๋ผ๋ฏธํฐ๋ฅผ ํ์๋กํ๋ ๊ฒฝ์ฐ์ ์ก์ ์์ฑ ํจ์๋ฅผ createAction ์ผ๋ก ๋ง๋ค์ด ๋ณด์
import { createAction } from 'redux-actions';
const CHANGE_INPUT = 'sample/CHANGE_INPUT'; // input์ ๋ณ๊ฒฝํ๋ ์ก์
const INSERT = 'sample/INSERT' // ์๋ก์ด todo๋ฅผ ๋ฑ๋กํ๋ ์ก์
export const changeInput = createAction(CHANGE_INPUT, input => input);
export const insert = createAction(INSERT, text => ({
id: id++,
text,
done: false,
}));
insert์ ๊ฒฝ์ฐ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์ก์ ๊ฐ์ฒด ์์ ๋ฃ์ด ์ฃผ์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ text๋ฅผ ๋ฃ์ผ๋ฉด
text๋ฅผ ์ถ๊ฐํ ์๋ก์ด ๊ฐ์ฒด๊ฐ ๋ฐํ๋๋ ํจ์๋ฅผ ๋ฃ์ด์ค๋ค
changeInput์ ๊ฒฝ์ฐ input => input ํํ๋ก ํ๋ผ๋ฏธํฐ๋ฅผ ๊ทธ๋๋ก ๋ฐํํ๋ ํจ์๋ฅผ ๋ฃ์์ง๋ง ์ด ์์ ์ด ํ์๋ ์๋๋ค.
์๋ตํด๋ ๋๊ฐ์ด ์๋ํ์ง๋ง, ์ฝ๋๋ฅผ ๋ณด์์ ๋ ์ด ์ก์ ์์ฑ ํจ์์ ํ๋ผ๋ฏธํฐ๋ก ์ด๋ค ๊ฐ์ด ํ์ํ์ง ์ฝ๊ฒ ํ์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ ๊ฒ ์์ฑํ๋๋ก ํ์
handleActions๋ก ๋ฆฌ๋์๋ฅผ ์ฌ์์ฑ ํด๋ณด๊ธฐ
createAction์ผ๋ก ๋ง๋ ์ก์ ์์ฑ ํจ์๋ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ๊ฐ์ ๊ฐ์ฒด์ ๋ฃ์ ๋ ์์์ ์ด๋ฆ์ผ๋ก ๋ฃ๋ ๊ฒ์ด ์๋,
action.payload๋ผ๋ ์ด๋ฆ์ ๊ณตํต์ ์ผ๋ก ๋ฃ์ด์ผ ํ๋ค
const todos = handleActions(
{
[CHANGE_INPUT]: (state, action) => ({ ...state, action.payload }),
[INSERT]: (state, action) => ({
...state,
todos: state.todos.concat(action.payload),
}),
[TOGGLE]: (state, action) => ({
...state,
todos: state.todos.map((todo) =>
todo.id === action.payload ? { ...todo, done: !todo.done } : todo
),
}),
[REMOVE]: (state, action) => ({
...state,
todos: state.todos.filter((todo) => todo.id !== action.payload),
}),
},
initialState
);
์๋์ ๊ฐ์ด ๋น๊ตฌ์กฐํ ํ ๋น ๋ฌธ๋ฒ์ ์ฌ์ฉํ์ฌ Action ๊ฐ์ payload ์ด๋ฆ์ ์๋ก ์ค์ ํด ์ฃผ๋ฉด payload๊ฐ ์ด๋ค ๊ฐ์ ์๋ฏธํ๋์ง ์ฝ๊ฒ ํ์ ์ด ๊ฐ๋ฅํ๋ค.
const todos = handleActions(
{
[CHANGE_INPUT]: (state, { payload: input }) => ({ ...state, input }),
[INSERT]: (state, { payload: todo }) => ({
...state,
todos: state.todos.concat(todo),
}),
[TOGGLE]: (state, { payload: id }) => ({
...state,
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo
),
}),
[REMOVE]: (state, { payload: id }) => ({
...state,
todos: state.todos.filter((todo) => todo.id !== id),
}),
},
initialState
);
payload์ ๊ฐ์ด ์ด๋ค ๊ฐ์ ๋ปํ๋์ง ํ๋์ ์ฝ๊ฒ ํ์ ๋์ด ๋ณด๊ธฐ ํธํด์ง๋ค !
Reference
(์ฑ ) ๋ฆฌ์กํธ๋ฅผ ๋ค๋ฃจ๋ ๊ธฐ์ - ๊น๋ฏผ์ค (VELOPERT)
'Redux' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Redux-Toolkit ์์ Saga ์ฌ์ฉํ๊ธฐ (Feat. Typescript) (0) | 2021.08.01 |
---|---|
Redux Toolkit ์ ๋ํ์ฌ ์์๋ณด๊ธฐ (0) | 2021.06.07 |
๋ฆฌ๋์ค์์ Hooks ์ฌ์ฉํ๊ธฐ (0) | 2021.05.25 |
๋ฆฌ๋์ค๋ ๋ฌด์์ธ๊ฐ ? (0) | 2021.05.20 |