
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import * as serviceWorker from './serviceWorker'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import rootReducer from './modules'
import { composeWithDevTools } from 'redux-devtools-extension'
// rootReducer 를 가진 Store 생성
const store = createStore(rootReducer, composeWithDevTools())
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
)
serviceWorker.unregister()제일 먼저 index.js 에서 createStore 함수를 이용해 Store 를 만들었다.
이 때 rootReducer 라는 모듈을 포함시켰는데 rootReducer 파일로 가보면
src/module/index.js
import { combineReducers } from 'redux'
import counter from './counter'
import working from './working'
const rootReducer = combineReducers({
counter,
working,
})
export default rootReducercombineReducers 함수로 counter 모듈과 working 모듈을 하나의 모듈로 합쳐,
rootReducer 라는 이름으로 export 시켜주고 있는걸 알 수 있다.
그렇기에 src/index.js 에서 rootReducer 로 호출하면
src/index.js 에 만들어진 Store 에는 counter 모듈과 working 모듈이 들어있는거다.
현 게시글에선 combineReducers 함수의 사용 예만 보기위해
working 모듈은 사용하지않고 counter 모듈만 사용했다.
src/module/counter.js
// Action type ( 액션 타입 )
const SET_DIFF = 'counter/SET_DIFF'
const INCREASE = 'counter/INCREASE'
const DECREASE = 'counter/DECREASE'
// Action Creator Function ( 액션 생성 함수 )
export const setDiff = diff => ({ type: SET_DIFF, diff })
export const increase = () => ({ type: INCREASE })
export const decrease = () => ({ type: DECREASE })
// init State ( 초기 상태 )
const initialState = {
number: 0,
diff: 1,
}
// Reducer function ( 리듀서 함수 )
export default function counter(state = initialState, action) {
switch (action.type) {
case SET_DIFF:
return {
...state,
diff: action.diff,
}
case INCREASE:
return {
...state,
number: state.number + state.diff,
}
case DECREASE:
return {
...state,
number: state.number - state.diff,
}
default:
return state
}
}잘 보면 액션과 액션생성함수, 리듀서 함수가 한 파일에 들어가 있다.
이는 어떤 동작에 관련되어진 액션과 액션생성함수, 리듀서 함수를 각각 다른 파일로 나누는 것이 아닌
하나로 합쳐 만드는 Ducks 패턴으로 작성된 모듈이다. 꼭 이렇게 할 필요는 없다.
Ducks 패턴으로 작성할 때는 위처럼 type 앞에 구분자를 넣어주어
모듈간 중복상황이 일어나지 않게 해줘야 한다.
src/index.js
다시 src/index.js 파일로 돌아와서
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
)
serviceWorker.unregister()Provider 는 react-rudex 의 기능 중 하나로 하위 컴포넌트들에게 공급해주는 역할을 하는 녀석이다. 로드해온 스토어의 상태등을 하위의 App 컴포넌트에 전달하며 렌더링한다.
src/App.js
import React from 'react'
import CounterContainer from './containers/CounterContainer'
function App() {
return (
<div>
<CounterContainer />
</div>
)
}
export default AppCounterContainer 컴포넌트를 렌더링하려 한다.
src/containers/CounterContainer.js
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import Counter from '../components/Counter'
import { increase, decrease, setDiff } from '../modules/counter'
function CounterContainer() {
const { number, diff } = useSelector(state => ({
number: state.counter.number,
diff: state.counter.diff,
}))
const dispatch = useDispatch()
const onIncrease = () => dispatch(increase())
const onDecrease = () => dispatch(decrease())
const onSetDiff = diff => dispatch(setDiff(diff))
return (
<Counter
number={number}
diff={diff}
onIncrease={onIncrease}
onDecrease={onDecrease}
onSetDiff={onSetDiff}
/>
)
}
export default CounterContaineruseSelector() 는 ReduxHooks 의 기능 중 하나로 리덕스 스토어의 상태를 조회해주는 기능을 수행한다.
useSelector 기능을 사용하게 되면 리덕스 스토어의 상태에 대해
이전 게시글에서 설명한 구독 ( subscribe ) 을 수행한다.
useDispatch() 도 ReduxHooks 의 기능 중 하나이며
리덕스 스토어의 dispatch 를 함수 컴포넌트에서 사용할 수 있도록 해주는 녀석이다.
이 덕에 onIncrease, onDecrease, onSetDiff 함수에서 dispatch 를 사용할 수 있다.
그런데 이 전 게시글에서 dispatch 는 action 을 인자로 던진다고 했는데 여기선 함수를 던지고 있다.
이는 아까 src/index.js 에서 counter 모듈을 가지고 있는 Store 를 로드 했는데,
counter 모듈에서 increase(), decrease(), setDiff() 를 액션 생성 함수로 선언 해놨기때문이다.
이렇게 Counter.js 컴포넌트를 렌더링하면서 프로퍼티로는 2 개의 값과 3 개의 함수를 보낸다.
src/components/Counter.js
import React from 'react'
function Counter({ number, diff, onIncrease, onDecrease, onSetDiff }) {
const onChange = e => {
onSetDiff(parseInt(e.target.value, 10))
}
return (
<div>
<h1>{number}</h1>
<div>
<input type="number" value={diff} min="1" onChange={onChange} />
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
</div>
)
}
export default Counterreturn 부분을 보면
onIncrease
onDecrease
CounterContainer.js 에서 넘어온 dispatch 를 실행하도록 걸어놨다.
자 지금까지 설명한 모든 과정을 마치고,
제일 처음에 렌더링을 시도했던 메인페이지인 src/index.js 로 돌아가 화면에 그려지게 되는 것이다.
그 후 숫자 증가 버튼을 클릭하게 되면
스토어에 내장되어있는 reducer 중 counter.js 모듈의 리듀서를 수행해 값이 증가되며
스토어의 상태에 View 가 이전의 useSelector 로 인해 구독이 되어있기에 상태 변화를 감지하여
값을 실시간으로 화면에 렌더링해 뿌려주는 것이다.