리덕스를 직접 사용해보자.
먼저 아래와 같이 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 로 인해 구독이 되어있기에 상태 변화를 감지,
값을 실시간으로 화면에 렌더링해 뿌려주는 것이다.
지금까지 리덕스를 실제로 리액트에 적용해보는 내용을 다루었다.
리덕스는 나의 기준으로는 리액트를 하면서 제일 이해가 더딘 부분이기도 하다.
출처 & 참고 : https://mjn5027.tistory.com/34
'Front-end > React' 카테고리의 다른 글
React에서 사용자 브라우저를 쉽게 감지하는 방법 react-device-detect (0) | 2022.01.04 |
---|---|
[React] import 중괄호 {}를 쓰는 이유 (0) | 2021.12.29 |
[ React ] 리액트 리덕스(Redux) 정의 (0) | 2021.12.28 |
[React] 크롬 React 디버깅 툴 설치 방법 (React Developer Tools) (0) | 2021.12.13 |
[VScode] 리액트 필수 플러그인 React 코드 템플릿 자동 완성 플러그인 @ Reactjs code snippets (0) | 2021.11.19 |
댓글