저번 게시글에선 리덕스의 정의에 대해서만 다루었고 이번 게시글에선 리덕스를 직접 사용해보자.
먼저 아래와 같이 CMD 에 입력해 프로젝트를 만든다.
C:\> create-react-app react-redux
C:\> cd react-redux
리덕스 모듈 redux 와 리덕스의 다양한 도구들이 들어있는 react-redux를 설치한다.
C:\react-redux> yarn add redux react-redux
react-redux 프로젝트를 열어보자.
디렉터리의 구조는 다음과 같이 만들었다.
소스를 흐름대로 살펴보도록 하겠다.
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( src/index.js 아님 )
import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';
const rootReducer = combineReducers({
counter,
todos
});
export default rootReducer;
combineReducers 함수로 counter 모듈과 todos 모듈을 하나의 모듈로 합쳐,
rootReducer 라는 이름으로 export 시켜주고 있는걸 알 수 있다.
그렇기에 src/index.js 에서 rootReducer 로 호출하면
src/index.js 에 만들어진 Store 에는 counter 모듈과 todos 모듈이 들어있는 셈.
현 게시글에선 combineReducers 함수의 사용 예만 보기위해
todos 모듈은 사용하지않고 counter 모듈만 사용했다.
그럼 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 앞에 구분자를 넣어주어 모듈간 중복상황이 일어나지 않게 해줘야 한다.
// Action type ( 액션 타입 )
const SET_DIFF = 'counter/SET_DIFF';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
자 그럼 현재까지 본 건 src/index.js 에서 위에서 본 counter 모듈과,
사용하진 않지만 todos 라는 모듈을 가진 스토어를 만들었다.
다시 src/index.js 파일로 돌아와서
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
Provider 는 react-rudex 의 기능 중 하나로 하위 컴포넌트들에게 공급해주는 역할을 하는 녀석이다.
로드해온 스토어의 상태등을 하위의 App 컴포넌트에 전달하며 렌더링한다.
App 컴포넌트를 렌더링하는 순서이니 이제 App.js 컴포넌트로 이동해보자.
src/App.js
import React from 'react';
import CounterContainer from './containers/CounterContainer';
function App() {
return (
<div>
<CounterContainer />
</div>
);
}
export default App;
CounterContainer 컴포넌트를 렌더링하려 한다. CounterContainer 로 이동.
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 CounterContainer;
이 코드에 대해 설명하자면 첫 번째 코드 블럭부터 살펴보면
const { number, diff } = useSelector(state => ({
number: state.counter.number,
diff: state.counter.diff
}));
useSelector() 는 ReduxHooks 의 기능 중 하나로 리덕스 스토어의 상태를 조회해주는 기능을 수행한다.
useSelector 기능을 사용하게 되면 리덕스 스토어의 상태에 대해 이전 게시글에서 설명한 구독 ( subscribe ) 을 수행한다.
// 리덕스 스토어의 dispatch 를 사용할 수 있도록 해줌.
const dispatch = useDispatch();
// 각 액션을 dispatch 하는 함수 생성
const onIncrease = () => dispatch(increase());
const onDecrease = () => dispatch(decrease());
const onSetDiff = diff => dispatch(setDiff(diff));
useDispatch() 도 ReduxHooks 의 기능 중 하나이며
리덕스 스토어의 dispatch 를 함수 컴포넌트에서 사용할 수 있도록 해주는 녀석이다.
이 덕에 onIncrease, onDecrease, onSetDiff 함수에서 dispatch 를 사용할 수 있다.
그런데 이 전 게시글에서 dispatch 는 action 을 인자로 던진다고 했는데 여기선 함수를 던지고 있다?
이는 아까 src/index.js 에서 counter 모듈을 가지고 있는 Store 를 로드 했는데,
이 counter 모듈에서 increase(), decrease(), setDiff() 를 액션 생성 함수로 아래와 같이 선언해놨다.
// 위 쪽에서 보여준 counter.js 모듈 안의 코드이다.
// Action Creator Function ( 액션 생성 함수 )
export const setDiff = diff => ({ type: SET_DIFF, diff });
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
counter 모듈의 저 세 개의 함수를 호출했고, 호출할 수 있는 이유는 export 로 선언했기 떄문이고,
그렇기에 CounterContainer.js 컴포넌트에서 dispatch(increase()) 식으로 사용할 수 있는 것이다.
increase() 는 Action 객체를 return 하기에 dispatch(action) 의 형태가 정상적으로 이루어지는 셈이다.
다시 CounterContainer.js 컴포넌트로 돌아가서 return 부분 코드를 보면,
return (
<Counter
number={number}
diff={diff}
onIncrease={onIncrease}
onDecrease={onDecrease}
onSetDiff={onSetDiff}
/>
);
이렇게 Counter.js 컴포넌트를 렌더링하면서 프로퍼티로는 2 개의 값과 3 개의 함수를 보낸다.
그럼 Counter.js 컴포넌트로 가보자.
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 Counter;
return 부분을 보면 <button> 태그에 onClick 이벤트로,
CounterContainer.js 에서 넘어온 dispatch 를 실행하도록 걸어놨다.
자 지금까지 설명한 모든 과정을 마치고,
제일 처음에 렌더링을 시도했던 메인페이지인 src/index.js 로 돌아가 화면에 그려지게 되는 것이다.
그 후 숫자 증가 버튼을 클릭하게 되면
스토어에 내장되어있는 reducer 중 counter.js 모듈의 리듀서를 수행해 값이 증가되며,
스토어의 상태에 View 가 이전의 useSelector 로 인해 구독이 되어있기에 상태 변화를 감지,
값을 실시간으로 화면에 렌더링해 뿌려주는 것이다.
지금까지 리덕스를 실제로 리액트에 적용해보는 내용을 다루었다.
리덕스는 나의 기준으로는 리액트를 하면서 제일 이해가 더딘 부분이기도 하다.
'Coding Story > REACT' 카테고리의 다른 글
[ React ] 리액트 리덕스 툴킷 redux-toolkit (3) | 2020.10.22 |
---|---|
[ React ] 리액트 Redux 로 게시판 만들어보기 (8) | 2020.10.22 |
[ React ] 리액트 리덕스(Redux)란? (0) | 2020.10.22 |
[ React ] 리액트 Axios (0) | 2020.10.22 |
[ React ] 리액트 Hooks(useState, useEffect) (0) | 2020.10.21 |