728x90
작업환경 설정
- 리액트 프로젝트를 생성한다.
- redux와 react-redux 라이브러리를 설치한다.
<code />
yarn add redux react-redux
UI 준비하기
- 리액트에서 리덕스를 사용할 때 가장 많이 사용하는 패턴
- → 프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 분리하는 것이다.
- 프레젠테이셔널 컴포넌트 : 주로 상태 관리가 이루어지지 않고, props만 받아와서 화면에 UI만 보여주는 컴포넌트
- 컨테이너 컴포넌트 : 리덕스와 연동이 되어있는 컴포넌트
- 이러한 패턴이 필수는 아니지만, 이 패턴을 사용하면 코드의 재사용성도 높아지고, 관심사의 분리가 이루어져 편리하다.

- 2개의 컴포넌트를 만드는 실습을 진행

리덕스 관련 코드 작성하기
- 보통 actions, constants, reducers 각각의 폴더를 만들고, 그 안에 기능별로 파일을 하나씩 만드는 방식을 사용한다.
- 하지만, 그렇게 되면 새로운 액션을 만들때 마다 3개의 파일을 모두 수정해야 하기 때문에 불편하다.
- 본 실습에서는 ducks 패턴을 사용한다.
- 액션 타입, 액션 생성 함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 다 작성하는 방식이다.
- ducks 패턴을 활용한 counter 모듈 작성 예시
<code />
import { createAction, handleActions } from 'redux-actions';
// 1. 액션 타입 정의
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
// 2. 액션 생성함수 만들기
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
// 3. 모듈의 초기 상태
const initialState = {
number: 0,
};
// 4. 리듀서 함수
function counter(state = initialState, action) { // state: 현재 상태, action: 전달받은 액션 객체
switch(action.type) {
case INCREASE:
return { // 현재 상태를 참조하여 새로운 객체를 반
counter: state.counter + 1
};
case DECREASE:
return {
counter: state.counter - 1
};
default:
return state;
}
}
export default counter;
- counter 모듈 말고도 todos 모듈을 생성한 상태에서 다음 실습을 진행한다.
- 모듈을 만드는 틀은 동일하기 때문에 생략한다.
- 또한 두 개의 리듀서를 만들었기 때문에 하나로 합쳐주어야 한다.
- 따라서 combineReducers를 사용하여 2개의 리듀서를 합친 rootReducer을 생성한다.
<code />import { combineReducers } from 'redux'; import counter from './counter'; import todos from './todos'; const rootReducer = combineReducers({ counter, todos, }); export default rootReducer;
- → createStore 함수를 사용하여 스토어를 만들 때는 리듀서를 하나만 사용해야하기 때문
리액트 애플리케이션에 리덕스 적용하기
- 리덕스 관련 코드를 작성했으면, 이제 적용을 해야 한다.
- 해야할 일은 크게 2가지이다.
- store을 생성한다.
- Provider 컴포넌트를 사용하여 프로젝트에 리덕스를 적용한다.
1. store 생성
- store은 src > index.js 파일에서 생성한다.
<code />
import React from 'react';
import ReactDOM from 'react-dom/client';
import { createStore } from 'redux';
import './index.css';
import App from './App';
import rootReducer from './modules';
**const store = createStore(rootReducer);**
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
2. Provider 컴포넌트를 사용하여 리덕스 적용
- App 컴포넌트를 Provider 컴포넌트로 감싸주며, store을 props로 전달한다.
<code />
root.render(
<React.StrictMode>
**<Provider store={store}>**
<App />
**</Provider>**
</React.StrictMode>
);
리액트 개발자 도구
redux dev tools
- 리덕스 개발자 도구이며 크롬 확장 프로그램으로 설치하여 사용할 수 있다.
- https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko
- 패키지로 설치하는 것도 가능하다.
<code />
yarn add redux-devtools-extension
<code />
import { composeWithDevTools } from 'redux-devtools-extension';
...
const store = createStore(rootReducer, composeWithDevTools());
...
컨테이너 컴포넌트 만들기
- 컴포넌트를 만들어 리덕스 스토어에 접근하여 원하는 상태를 받아오고, 또 액션도 디스패치 해준다.
- 리덕스 스토어와 연동된 컴포넌트를 컨테이너 컴포넌트라고 한다.
counter 컨테이너 만들기
1. src > containers 디렉터리를 생성하고 그 안에 CounterContainer 컴포넌트를 생성한다.
<code />
import Counter from '../components/Counter';
const CounterContainer = () => {
return (
<Counter />
);
};
export default CounterContainer;
2. 위 컴포넌트와 redux를 연결하기 위해 react-redux에서 제공하는 connect 함수를 사용한다.
<code />
connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)
3. 예시
<code />
import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
export default connect(
// connect의 첫 번째 인자: 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수
state => ({
number: state.counter.number
}),
// connect의 두 번째 인자: 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수
// 액션 생성 함수로 이루어진 객체 형태로 넣어준다.
{
increase,
decrease
}
)(CounterContainer);
4. 사용

- 왼쪽에 `action이름`, `store 내에 저장된 state값` 등이 실시간으로 보여지는 것을 볼 수 있다.
리덕스 더 편하게 사용하기
액션 생성 함수 및 리듀서 작성 시 → redux-actions, immer 라이브러리를 활용하면 리덕스를 훨씬 편하게 사용할 수 있다.
1. redux-actions
- 액션 생성 함수를 더 짧은 코드로 작성할 수 있다.
- 기존 액션함수 생성
<javascript />
```jsx
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
```
- redux-actions 사용
<code />
import { createAction } from 'redux-actions';
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
- createAction 함수를 사용하면 매번 객체를 생성할 필요 없이 더욱 간단하게 액션 생성 함수를 선언할 수 있다.
- 기존 리듀서 함수 생성
<code />
function counter(state = initialState, action) { // state: 현재 상태, action: 전달받은 액션 객체
switch(action.type) {
case INCREASE:
return { // 현재 상태를 참조하여 새로운 객체를 반
counter: state.counter + 1
};
case DECREASE:
return {
counter: state.counter - 1
};
default:
return state;
}
}
- redux-actions 사용
<code />
const counter = handleActions(
{
[INCREASE]: (state, action) => ({ number: state.number + 1 }),
[DECREASE]: (state, action) => ({ number: state.number - 1 }),
},
initialState,
);
2. immer
- 리듀서에서 상태를 업데이트 할 때는 불변성을 지켜야 하기 때문에 앞에서는 spread 연산자와 배열의 내장함수를 활용했다.
- 하지만, 모듈의 상태가 복잡해질수록 불변성을 지키기 까다로워진다.
- 객체의 깊이가 깊어질 수록 추후 불변성을 지키기 위해 immer을 사용한다.
<code />
import { produce } from 'immer';
...
const todos = handleActions(
{
...
[TOGGLE]: (state, { payload: id }) =>
produce(state, draft => {
const todo = draft.todos.find(todo => todo.id === id);
todo.done = !todo.done;
}),
...
},
initialState,
);
export default todos;
Hooks를 사용하여 컨테이너 컴포넌트 만들기
- 리덕스 스토어와 연동된 컨테이너 컴포넌트를 만들때 connect 함수를 사용한다고 했었다.
- 하지만, 그것 대신 react-redux에서 제공하는 Hooks를 사용할 수도 있다.
1. useSelector으로 상태 조회하기
- useSelector을 사용하면 connect 함수를 사용하지 않고도 리덕스의 상태조회를 할 수 있다.
<code />
const 결과 = useSelector(상태 선택 함수);
- 예시
<code />
const number = useSelector(state => state.counter.number);
2. useDispatch를 사용하여 액션 디스패치하기
- 컴포넌트 내부에서 스토어의 내장함수 dispatch를 사용할 수 있게 해준다.
- 컨테이너 컴포넌트에서 액션을 디스패치해야할 때 사용한다.
- 사용
<code />
import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';
const CounterContainer = () => {
...
const dispatch = useDispatch();
return (
<Counter number={number}
onIncrease={() => dispatch(increase())}
onDecrease={() => dispatch(decrease())}
/>
);
};
export default CounterContainer;
3. useStore을 사용하여 리덕스 스토어 사용하기
- 컴포넌트 내부에서 스토어 객체를 직접 사용할 수 있다.
- 사용
<code />
const store = useStore();
store.dispatch({ type: 'SAMPLE_ACTION' });
store.getState();
- 이는 어쩌다가 스토어에 접근해야하는 상황에만 사용해야 한다.
정리
- 리액트 프로젝트에 리덕스를 적용하는 방법을 배웠다.
- 업데이트에 관련된 로직을 리액트 컴포넌트에서 완벽하게 분리시킬 수 있으므로 유지보수성이 높은 코드를 작성할 수 있다.
- 이번 프로젝트 처럼 정말 작은 프로젝트에 리덕스를 적용하면, 오히려 프로젝트의 복잡도가 증가할 수 있지만, 규모가 큰 프로젝트에 리덕스를 적용하면 상태를 더 체계적으로 관리할 수 있다.
'React' 카테고리의 다른 글
[React] 서버 사이드 렌더링 (0) | 2023.07.03 |
---|---|
[React] 코드 스플리팅 (0) | 2023.07.03 |
[React] 리덕스 라이브러리 (0) | 2023.07.01 |
[React] Context API (0) | 2023.06.30 |
[React] 리액트 컴포넌트에서 API를 연동 (0) | 2023.06.28 |