끄적끄적 코딩
article thumbnail

작업환경 설정

  1. 리액트 프로젝트를 생성한다.
  2. redux와 react-redux 라이브러리를 설치한다.
yarn add redux react-redux


UI 준비하기

  • 리액트에서 리덕스를 사용할 때 가장 많이 사용하는 패턴
    • → 프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 분리하는 것이다.
    • 프레젠테이셔널 컴포넌트 : 주로 상태 관리가 이루어지지 않고, props만 받아와서 화면에 UI만 보여주는 컴포넌트
    • 컨테이너 컴포넌트 : 리덕스와 연동이 되어있는 컴포넌트
  • 이러한 패턴이 필수는 아니지만, 이 패턴을 사용하면 코드의 재사용성도 높아지고, 관심사의 분리가 이루어져 편리하다.

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

리덕스 관련 코드 작성하기

  • 보통 actions, constants, reducers 각각의 폴더를 만들고, 그 안에 기능별로 파일을 하나씩 만드는 방식을 사용한다.
  • 하지만, 그렇게 되면 새로운 액션을 만들때 마다 3개의 파일을 모두 수정해야 하기 때문에 불편하다.
  • 본 실습에서는 ducks 패턴을 사용한다.
  • 액션 타입, 액션 생성 함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 다 작성하는 방식이다.
  • ducks 패턴을 활용한 counter 모듈 작성 예시
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을 생성한다.
    import { combineReducers } from 'redux';
    import counter from './counter';
    import todos from './todos';
    
    const rootReducer = combineReducers({
      counter,
      todos,
    });
    
    export default rootReducer;
    
  • → createStore 함수를 사용하여 스토어를 만들 때는 리듀서를 하나만 사용해야하기 때문

 

리액트 애플리케이션에 리덕스 적용하기

  • 리덕스 관련 코드를 작성했으면, 이제 적용을 해야 한다.
  • 해야할 일은 크게 2가지이다.
    1. store을 생성한다.
    2. Provider 컴포넌트를 사용하여 프로젝트에 리덕스를 적용한다.

1. store 생성

  • store은 src > index.js 파일에서 생성한다.
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로 전달한다.
root.render(
  <React.StrictMode>
  **<Provider store={store}>**
    <App />
  **</Provider>**
  </React.StrictMode>
);

 

리액트 개발자 도구

redux dev tools

yarn add redux-devtools-extension
import { composeWithDevTools } from 'redux-devtools-extension';
...
const store = createStore(rootReducer, composeWithDevTools());
...

 

컨테이너 컴포넌트 만들기

  • 컴포넌트를 만들어 리덕스 스토어에 접근하여 원하는 상태를 받아오고, 또 액션도 디스패치 해준다.
  • 리덕스 스토어와 연동된 컴포넌트를 컨테이너 컴포넌트라고 한다.

counter 컨테이너 만들기

1. src > containers 디렉터리를 생성하고 그 안에 CounterContainer 컴포넌트를 생성한다. 

import Counter from '../components/Counter';

const CounterContainer = () => {
  return (
    <Counter />
  );
};

export default CounterContainer;


2. 위 컴포넌트와 redux를 연결하기 위해 react-redux에서 제공하는 connect 함수를 사용한다.

connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)


3. 예시

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

  • 액션 생성 함수를 더 짧은 코드로 작성할 수 있다.
  • 기존 액션함수 생성
```jsx
export const increase = () => ({ type: INCREASE }); 
export const decrease = () => ({ type: DECREASE }); 
```
  • redux-actions 사용
import { createAction } from 'redux-actions';

export const increase = createAction(INCREASE); 
export const decrease = createAction(DECREASE); 
  • createAction 함수를 사용하면 매번 객체를 생성할 필요 없이 더욱 간단하게 액션 생성 함수를 선언할 수 있다.
  • 기존 리듀서 함수 생성
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 사용
const counter = handleActions(
  {
    [INCREASE]: (state, action) => ({ number: state.number + 1 }),
    [DECREASE]: (state, action) => ({ number: state.number - 1 }),
  },
  initialState,
);


2. immer

  • 리듀서에서 상태를 업데이트 할 때는 불변성을 지켜야 하기 때문에 앞에서는 spread 연산자와 배열의 내장함수를 활용했다.
  • 하지만, 모듈의 상태가 복잡해질수록 불변성을 지키기 까다로워진다.
  • 객체의 깊이가 깊어질 수록 추후 불변성을 지키기 위해 immer을 사용한다.
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 함수를 사용하지 않고도 리덕스의 상태조회를 할 수 있다.
const 결과 = useSelector(상태 선택 함수);
  • 예시
const number = useSelector(state => state.counter.number);


2. useDispatch를 사용하여 액션 디스패치하기

  • 컴포넌트 내부에서 스토어의 내장함수 dispatch를 사용할 수 있게 해준다.
  • 컨테이너 컴포넌트에서 액션을 디스패치해야할 때 사용한다.
  • 사용
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을 사용하여 리덕스 스토어 사용하기

  • 컴포넌트 내부에서 스토어 객체를 직접 사용할 수 있다.
  • 사용
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

검색 태그