์์ํ๋ฉฐ
App์ด ์ ์ ๊ท๋ชจ๊ฐ ์ปค์ง๋ฉด์ ์ํ ๊ด๋ฆฌํ ํ์๋ฅผ ๊ณ์ ๋๋ผ๊ณ ์์๋๋ฐ, ์ด๋ฒ์์ผ ๋์ ํ๋ค. ํ์ํ ๋ชจ๋์ ์ค์นํ๊ณ , ๊ฐ๋จํ๊ฒ +/- ๋ฒํผ์ ํตํด ์นด์ดํธ๋๋ ๊ธฐ๋ฅ์ ๋ง๋ค์ด๋ณด๋ฉด์ Redux์ ์ ๊ทผํด๋ดค๋ค.
ํ์ ๋ชจ๋ ๋ค์ด๋ก๋
yarn add react-redux redux redux-actions
yarn์ ํตํด ์ dependencies๋ฅผ ๋ฐ์๋ค.
ํด๋ ๊ตฌ์ฑ
์ต์ข ํด๋ ๊ตฌ์ฑ์ ์ฌ์ง์ฒ๋ผ ๋์ด ์๋ค. src/store ๋ด config.js, index.js ํ์ผ์ ๊ฐ์ง๋ฉฐ, modules ํด๋๋ฅผ ๋ฐ๋ก ๋์๋ค.
store
1. config.js ํ์ผ ์์ฑ
ํด๋น ํ์ผ์ ์ฌ๋ฌ store๋ฅผ ๋ชจ์ ์ธ๋ถ ์ปดํฌ๋ํธ์ ์ฐ๊ฒฐํ๊ธฐ ์ํจ์ด๋ค. store๋ state๋ฅผ ๊ด๋ฆฌํ๋ ์ผ์ข ์ ๋ณด๊ด์ฒ.
// src/config.js
import { createStore } from 'redux'; // ์ฌ๋ฌ store๋ฅผ ์ ๊ณตํ ์ ์๋ ๊ธฐ๋ณธ ํ๊ฒฝ์ ๋ง๋ ๋ค.
์ฐ์ config.js ํ์ผ์ ์์ฒ๋ผ ๋ง๋ค์ด๋๊ณ ์์ํ๋ค.
2. store ๋๋ ํ ๋ฆฌ ์๋์ modules ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑ
modules
โ index.js
โ test.js
- modules : state๋ฅผ ๊ด๋ฆฌํ๋ ๋ชจ๋ store๊ฐ ๋ด๊ธฐ๋ ๋๋ ํ ๋ฆฌ๋ก, ์์ผ๋ก ์ถ๊ฐ๋ ๋ชจ๋ store๋ ์ด ๋๋ ํ ๋ฆฌ ๋ด์์ ๊ด๋ฆฌํ๋ค.
- test.js : test๋ฅผ ์ํ store ํ์ผ
2-1. test.js ํ์ผ ์์ฑ
// src/moduels/test.js
import { createAction, handleActions } from 'redux-actions';
const initialState = {
num: 0
};
export default handleActions({
// ...
}, initialState);
redux-actions
๋ก๋ถํฐ ํธ์ถํ๋ ๋ ํจ์๋ store ๋ด actions๋ฅผ ์ ์ดํ๋ค. ์ ์ฝ๋์์๋ num์ด๋ผ๋ ์ด๋ฆ์ state๊ฐ ์์ฑ๋์๋ค.
2-2. index.js ํ์ผ ์์ฑ
// src/modules/index.js
import { combineReducers } from 'redux';
import test from './test';
export default combineReducers({
test
})
modules
๋๋ ํ ๋ฆฌ ๋ด store ํ์ผ์ด ์ฐจ๊ณก ์ฐจ๊ณก ์์ด๋ฉด, ์ด๋ฅผ ํ๊ณณ์์ ๋ชจ์ ๋ด๋ณด๋ด๋ ๊ฒ์ด ํ์ํด์ง๋ค. ์ธ๋ถ์์ store๊ฐ ํ์ํด์ง ๋๋ src/store/moduels/index.js๋ฅผ ํธ์ถํ๋ฉด์ ๋ชจ๋ store์ ์ ๊ทผํ๋๋ก ํ๋ค.
3. config.js ํ์ผ ์์
// src/config.js
import { createStore } from 'redux';
import modules from './modules'; // store๊ฐ ๋ด๊ธด ์ ๋ณด๋ฅผ ํธ์ถํ๋ค.
const configure = () => { // devTools๋ผ๋ ๋ณ์์ Redux Store์ ํ๊ฒฝ ์ค์ ์ ๋ด๋๋ค.
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = createStore(modules, devTools);
return store;
}
export default configure;
1๋ฒ์์ createStore
ํจ์๋ง ํธ์ถํ๊ณ ๋ง config.js๋ก ๋์๊ฐ ์์ฒ๋ผ ์์ฑํด์ค๋ค.
์ฌ๊ธฐ์ createStore์ ๋ ๋ฒ์งธ ์ธ์๋ ์ ํ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋๋ฐ, redux์์ ์ ๊ณตํ๋ devTool์ ์ฐ๊ณ ์ ํ ๊ฒฝ์ฐ์ ์ถ๊ฐํด์ฃผ๋ฉด ๋๋ค. ์ฐ์ Firefox๋ Chrome์์ addon ๋๋ extension์ ๋ฐ์์ค๋ค.
์ด๋ฅผ ๋ฐ์์ค ํ, ์ฝ๋ ์์์ ํด๋น ์ค์ ์ ๋ง์น๋ฉด
์ด๋ฐ DevTool์ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค. ์์ counter์ isLogged๊ฐ ์๋ ๋ชจ์ต์, ํ ์คํธ ์ผ์ counterReducer์ isLoggedReducer๋ฅผ ๋ง๋ค๊ณ ์ด๊น๊ฐ์ ๊ฐ๊ฐ 0, false๋ก ์ง์ ํด์ฃผ์ด์ ์ด๋ฅผ ๋ฐ์ํด ๋ํ๋ ๋ชจ์ต์ด๋ค.
3-1. index.js ํ์ผ์ ์์ฑํ๋ค.
// src/store/index.js
import configure from './config';
export default configure();
์ด์ config ํจ์๋ฅผ ์ข ๋ ์ฝ๊ฒ ์ ๊ทผํ๋๋ก index.js ํ์ผ์ ์์ฑํด ์์ฒ๋ผ ์์ฑํด์ค๋ค.
4. redux๋ฅผ ์ฌ์ฉํด๋ณด๊ธฐ
4-1. src/index.js ์์
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import { BrowserRouter } from 'react-router-dom';
import './config/lang/i18n'
import { Provider } from 'react-redux'; // react-redux ๋ชจ๋๋ก๋ถํฐ Provider๋ฅผ ํธ์ถํ๋ค.
import Store from './store/index.js'; // store์ ์๊น ์์ฑํ index.js๋ฅผ ๋ถ๋ฌ์จ๋ค.
ReactDOM.render(
<Provider store={Store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
๋ฒ์ญ์ ์ํด i18n ๋ชจ๋๊ณผ router ์ฌ์ฉ์ ์ํด BrowserRouter๋ฅผ ํธ์ถํ๊ณ ์๋ ๋ชจ์ต์ด๋ค. ์๋ <BrowserRouter>
ํ๊ทธ๋ฅผ ๊ฐ์ธ๋ ํ๊ทธ๋ก, <React.StrictMode>
๊ฐ ์์๋ค. ํด๋น ํ๊ทธ๋ฅผ <Provider store={Store}>
๋ก ์์ ํด์ค๋ค.
4-2. src/App.js ์์
// src/App.js
import React, { Component } from 'react';
import './App.css';
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as testAction from './store/modules/test';
class App extends Component {
render() {
return(
<div className='App'>
<h1> Redux Test </h1>
<div>
<button> + </button>
{/* num */}
<button> - </button>
</div>
</div>
)
}
}
export default App;
ํด๋์ค ์ปดํฌ๋ํธ๋ก ์์ฑ๋์๋ค๋ฉด ์์ฒ๋ผ UI๋ฅผ ์์ฑํด์ค ์ ์์ ๊ฒ์ด๋ค. ํจ์ํ์ผ๋ก ์์ฑ๋ App.js๋ ์๋์ ๊ฐ๋ค. ์๋ ์ฝ๋์์๋ ์ฝ๋ ํ๋จ์ App์ defaultProps๋ฅผ ์ ์ํด์ค๋ค.
import React from 'react';
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as testAction from './store/modules/test';
function App() {
return (
<div className='App'>
<h1> Redux Test </h1>
<div>
<button> + </button>
{/* num */}
<button> - </button>
</div>
</div>
)
}
App.defaultProps = {
num: 0 // component์ Props๋ค์ ๊ธฐ๋ณธ ๊ฐ์ ์ง์ ํ๋ค.
}
export default connect( // react-redux ๋ชจ๋์ ๊ธฐ๋ฅ, store์ store์ state ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์จ๋ค.
(state) => ({ // ์ฌ์ฉํ state๋ฅผ ๊ฐ์ง๊ณ ์จ๋ค.
num: state.test.num // state.storeName.stateName ์ผ๋ก ๊ฐ์ง๊ณ ์จ๋ค.
}),
(dispatch) => ({ // ์ปดํฌ๋ํธ ์์์ ์ฌ์ฉํ Action ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์จ๋ค.
testAction: bindActionCreators(testAction, dispatch), // ์ธ์๊ฐ์ผ๋ก store๋ก๋ถํฐ ํธ์ถ๋ ์ ๋ณด๋ฅผ ๋ด๋๋ค.
})
)(App); // ํ์ฌ ์ปดํฌ๋ํธ์ ์ด๋ฆ
num: state.test.num
์ฝ๋์test
๋ ๋ฐ๋์ modules/index.js์ combineReducers ํจ์์ ์ธ์๋ก ๋ด์ ๊ฐ์ฒด์ ์์ฑํ ๊ฒ๊ณผ ๊ฐ์์ผ ํ๋ค.
- state ์ญ์ test.js์์ ์์ฑํ initialState์ num ๋ณ์๊ฐ ์๊ธฐ ๋๋ฌธ์ ๊ฐ์ง๊ณ ์ฌ ์ ์์๋ค.
4-3. ์ปดํฌ๋ํธ์์ state ๊ฐ ๋ฟ๋ ค์ฃผ๊ธฐ
// src/App.js
// ํด๋์คํ ์ปดํฌ๋ํธ์ ๊ฒฝ์ฐ this.props ๋ฅผ ํตํด num์ ์ ๊ทผํ๋ค.
<button> + </button>
ใ{ this.props.num }ใ
<button> - </button>
// ํจ์ํ ์ปดํฌ๋ํธ์ ๊ฒฝ์ฐ
function App(props) {
return (
<button> + </button>
{ props.num }ใ
<button> - </button>
)
}
4-4. ๋ฒํผ์ ์กฐ์ํ์ ๋ state ๊ฐ ๋ณ๊ฒฝํ๊ธฐ
์ปดํฌ๋ํธ์์๋ Action์ ์ธ์๋ก ๋ฐ๋ Reducer๋ฅผ ์คํํ๋ฉด์ state ๊ฐ ๋ณ๊ฒฝ์ ์๋ํ๋ค. ์ด๋ฅผ ์ํด Action์ ์ง์ ํด์ฃผ์ด์ผ ํ๋ค.
๋ค์, modules ๋๋ ํ ๋ฆฌ์ test.js๋ก ๋์๊ฐ ์๋์ฒ๋ผ ์ฝ๋๋ฅผ ์์ ํ๋ค.
// src/modules/test.js
import { createAction, handleActions } from 'redux-actions';
// ์ปดํฌ๋ํธ ๋ด์์ reducer ํจ์๋ฅผ ์ฐ๊ธฐ ์ํด Acton์ ๋ง๋ค์ด์ผ ํ๋ฏ๋ก, Action์ ์์ฑํ๊ณ , ์ด๋ฅผ ๋ค๋ฃจ๋ ํจ์๋ฅผ redux-actions ๋ชจ๋๋ก๋ถํฐ ํธ์ถํ๋ค.
const CHANGENUMBER = 'test/change_number';
// ๋๋ฌธ์๋ก ์์ํ๋ ๋ณ์์ Action์ ๋ํ ๊ฒฝ๋ก๋ฅผ ๋ด๋๋ค. ์ด ๊ฒฝ๋ก๊ฐ์ ์ค๋ณต๋์ง ์์์ผ ํ๋ค.
export const change_number = createAction(CHANGENUMBER);
// ๊ฒฝ๋ก๋ฅผ ๋ด์ ๋ณ์๋ฅผ createAction ํจ์์ ์ธ์๋ก ๋ฃ๋๋ค.
// ์ด์ ์ธ๋ถ์์ ์ ๊ทผํ ์ ์๋๋ก exportํ๋ค.
const initialState = {
num: 0
}
export default handleActions({
// handleActions์ ์ธ์๋ก ๋ค์ด๊ฐ๋ ๊ฐ์ฒด์ ํค๊ฐ ๋ CHANGENUMBER ์์.
[CHANGENUMBER] : (state, data) => {
let num = state.num;
// ์ฒซ ๋ฒ์งธ ์ธ์์ ๊ธฐ์กด state ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์ฌ ์ ์๋ ๊ฐ์ ๋ด๋๋ค.
// ๋ ๋ฒ์งธ ์ธ์์๋ ์ปดํฌ๋ํธ์์ reducer ํจ์๋ฅผ ์คํํ ๋ ์ ๋ฌ๋ฐ์ ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋๋ค.
if (data.payload.bool) {
num += 1;
} else {
num -= 1;
}
return {
...state, // ์ด ์ ๊ฐ ์ฐ์ฐ์๋ฅผ ํตํด Action ์๋ ์ ๋ชจ๋ state๊ฐ ์ด๊ธฐํ๋๋ ๊ฒ์ ๋ฐฉ์งํ๋ค.
num: num
}
}
}, initialState);
์ด์ ์ปดํฌ๋ํธ์์ reducer ํจ์๋ฅผ ์คํํ ๋ ์ฐธ์กฐํ ์ ์๋ Action์ด ๋ง๋ค์ด์ก๋ค. ์ปดํฌ๋ํธ๋ก ๋ค์ ๋์๊ฐ์,
// src/App.js
import React from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as testAction from './store/modules/test';
function App(props) {
const _changeNumber = (bool) => {
const { testAction } = props; // test store๋ฅผ props๋ก ํธ์ถ
testAction.change_number({'bool' : bool }) // test store์ change_number ๊ฒฝ๋ก๋ก ์ ๋ฌ
}
// ์ ํจ์ ์คํ ์ state ๋ณํ๋ฅผ ์ฃผ๋ reducer๊ฐ ์๋ํ๊ณ , ์ด๋ ์ฐธ์กฐ๋๋ Action์ผ๋ก test store์ ์ ์๋ change_number์ ๊ฒฝ๋ก๊ฐ ์ธ์๋ก ์ ๋ฌ๋๋ฉฐ num state๊ฐ ๋ณ๊ฒฝ๋๋ค.
return (
<h1>Redux Test</h1>
<div>
<button onClick={() => {
console.log('clicked');
_changeNumber(true)
} }>+</button>
{props.num}
<button onClick={() => _changeNumber(false)}>-</button>
)
}
App.defaultProps = {
num: 0
}
export default connect(
(state) => ({
num: state.test.num
}),
(dispatch) => ({
testAction: bindActionCreators(testAction, dispatch),
})
)(App);
์ด๋ฐ ์์ผ๋ก ๊ฐ๋จํ๊ฒ ๋ฆฌ๋์ค ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์๊ณ , ์ ์ฉํด๋ดค๋ค.
์์ฝ
- store : ํ๋ก์ ํธ์์ ์ฌ์ฉํ๋ ๋์ ๋ฐ์ดํฐ๊ฐ ๋ด๊ธฐ๋ ์ผ์ข ์ ์ค์ ์ ์ฅ์, ๋ณด๊ด์(Globalized State)
- action : ๋ฌด์์ ํ ์ง๋ฅผ ์ ์ํ๋ ๋ถ๋ถ. counter๋ฅผ ๋ง๋ ๋ค๊ณ ํ๋ค๋ฉด ์ซ์๋ฅผ 'Increment' ํ๋ค ํ์ ๋ ๋ฐ๋ก ๊ทธ 'increment' ๋ถ๋ถ
- reducer : action ๊ฐ์ฒด๋ฅผ ๋ฐ์์ ๋, ์ด ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๋ฐ๊ฟ์ง ๊ทธ ๋ฐฉ๋ฒ์ ์ ์ํ๋ ๊ฐ์ฒด.
- dispatch : action ๊ฐ์ฒด๋ฅผ reducer์ ์ ๋ฌํ๋ฉด์ ์ค์ง์ ์ผ๋ก ์ด ๊ณผ์ ์ด ์ผ์ด๋๋๋ก execute ํ๋ ๋ถ๋ถ
์ํ ๋ณ๊ฒฝ์ ์๋ ์์๋ก ์ด๋ฃจ์ด์ง๋ค.
- dispatch๋ก action ๊ฐ์ฒด๋ฅผ ๋ณด๋ธ๋ค.
- reducer๋ action ๊ฐ์ฒด๋ฅผ ์ธ์๋ก ๋ฐ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ค.
- ๋ณ๊ฒฝ๋ ์ํ๋ props๋ก ์ ๋ฌ๋ฐ์ ์ ์๋ค.
์ข ๋ ๊ฐ๋จํ๊ฒ๋ Redux Toolkit์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
๋ค์ ๋ฒ์๋ ์ฌ๋ฌ ์ปดํฌ๋ํธ์ ์ ์ญ์ ์ผ๋ก ์ฌ์ฉ๋๋ data๋ฅผ state๋ก ๊ด๋ฆฌํ๋๋ก ๋ฆฌํฉํ ๋ง์ ํด๋ด์ผ๊ฒ ๋ค.