본문 바로가기
Front-end/React

[ React ] 리액트 리덕스(Redux) 사용& 적용

by 꼬바리 2021. 12. 28.

리덕스를 직접 사용해보자.

먼저 아래와 같이 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 로 인해 구독이 되어있기에 상태 변화를 감지,

값을 실시간으로 화면에 렌더링해 뿌려주는 것이다.

 

 

지금까지 리덕스를 실제로 리액트에 적용해보는 내용을 다루었다.

리덕스는 나의 기준으로는 리액트를 하면서 제일 이해가 더딘 부분이기도 하다.

728x90
반응형

댓글