Front-end/React

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

꼬바리 2021. 12. 28. 17:46

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

먼저 아래와 같이 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
반응형