리액트 프로넥트에서 리덕스를 사용할 때 가장 많이 사용하는 패턴은 프레젠테이셔널 컴포넌트
와 컨테이너 컴포넌트
를 분리하는 것이다.
✔️ 이러한 패턴은 필수는 아니지만 이런 패턴을 사용하면 코드의 재사용성
도 높아지고, UI
에 더 집중할 수 있다.
//components/Counter.js
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;
//App.js
import React from 'react';
import Counter from './components/Counter';
const App = () =>{
return (
return (
<div>
<Counter number ={0}/>
</div>
)
)
}
export default App;
- 액션 타입은
대문자
로 정의하고, 문자열 내용은모듈 이름/액션이름
형식으로 작성- 모듈 이름을 넣음으로써 나중에 액션 이름이 충돌되지 않도록 함
//modules/counter.js
//액션타입
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
// 액션함수
// 앞부분에 export 키워드 들어가서 추후 이 함수를 다른 파일에서 불러와 사용할 수 있음.
export const increase = ()=>({type:INCREASE})
export const decrease = ()=>({type:DECREASE})
// modules/counter.js
(...)
//초기함수
const initialState={
number:0
}
//리듀서 함수 생성
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;
//export는 여러개 보낼 수 있지만
//default는 하나만 내보낼 수 있다.
import counter from './counter';
import {increase, decrease} from './counter';
import counter, {increase,decrease} from './counter';
만약 리듀서를 여러개 만들었을 경우, 애플리케이션에서 스토어는 하나만 존재해야하기에 여러개의 리듀서를 하나로 합쳐주어야 한다.
// modules/index.js
import {combineReducers} from 'redux';
import counter from './counter';
import todos from './todos';
const rootReducer = combineReducers({
counter,
todos,
})
export default rootReducer;
import rootReducer from './modules'
reate-redux에서 제공하는
Provider 컴포넌트
를 사용하여 store를 props로 전달해준다.
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import rootReducer from './modules';
import { legacy_createStore as createStore } from 'redux'
//reate-redux에서 제공하는 Provider 컴포넌트를 사용하여 store를 props로 전달해준다.
import { Provider } from 'react-redux';
const store = createStore(rootReducer)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
reportWebVitals();
$ yarn add redux-devtools-extension
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import rootReducer from './modules';
import { legacy_createStore as createStore } from 'redux'
import { Provider } from 'react-redux';
import { composeWithDevTools } from 'redux-devtools-extension'; //Redux DevTools
//composeWithDevTools()===> Redux DevTools 적용
const store = createStore(rootReducer, composeWithDevTools())
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
reportWebVitals();
//containers/CounterContainer.js
import React from 'react';
import Counter from '../components/Counter';
const CounterContainer=()=>{
return <Counter/>
};
export default CounterContainer;
connect 함수
를 사용해야 합니다.connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)
//containers/CounterContainer.js
import React from 'react';
import Counter from '../componenets/Counter';
import { connect } from 'react-redux';
import { decrease, increase } from '../modules/counter';
const CounterContainer =({number, increase, decrease})=>{
return <Counter number={number} onIncrease={increase} onDecrease={decrease}/>;
}
const mapStateToProps= state =>({
//state를 파라미터로 받아오고, 이 값은 현재 스토어가 지니고 있는 상태를 가르킨다.
number : state.counter.number,
});
// 비구조화 할당을 통해 counter를 분리하여 사용
//const mapStateToProps= ({counter})=>({
// number : counter.number
//})
const mapDispatchToProps= dispatch =>({
//mapDispatchToProps는 스토어의 내장함수 dispatch를 내장함수로 받아온다.
increase: () =>{
dispatch(increase())
},
decrease : () =>{
dispatch(decrease())
}
})
export default connect(mapStateToProps,mapDispatchToProps)(CounterContainer);
// src/App.js
// import './App.css';
import Todos from './componenets/Todos';
import CounterContainer from './containers/CounterContainer';
function App() {
return (
<div>
<CounterContainer/>
</div>
);
}
export default App;
//containers/CounterContainer.js
import React from 'react';
import Counter from '../componenets/Counter';
import { connect } from 'react-redux';
import { decrease, increase } 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()),
//increase:()=>{return dispatch(increase())},
decrease:()=>dispatch(decrease())
})
)(CounterContainer)
- 컴포넌트에서 액션을 디스패치하기 위해 각 액션 생성 함수를 호출하고 dispatch로 감싸는 작업이 번거롭습니다. 액션 생성함수의 개수가 많다면 더욱 그렇습니다.
- redux에서 제공하는
bindActionCreators 유틸 함수
를 사용해서 간편하게 구현할 수 있습니다.
//containers/CounterContainer.js
import React from 'react';
import Counter from '../componenets/Counter';
import { connect } from 'react-redux';
import { decrease, increase } from '../modules/counter';
import { bindActionCreators } from 'redux';
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)
두번째 파라미터를 아예
객체형태
로 넣어주면 connect 함수가 내부적으로bindActionCreators
작업을 대신 해줍니다.
//containers/CounterContainer.js
import React from 'react';
import Counter from '../componenets/Counter';
import { connect } from 'react-redux';
import { decrease, increase } 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)