상세 컨텐츠

본문 제목

[React.js 1팀] 17장 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기

24-25/React.js 1

by mingging17 2025. 1. 31. 10:01

본문

728x90

 

 

17장 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기

  • 작은 프로젝트에서는 state만으로 충분하지만, 프로젝트가 커질수록 상태 관리가 복잡해질 수 있다.
  • 리덕스를 사용하면 상태 업데이트 로직을 모듈화 하여 컴포넌트와 분리해 관리할 수 있어 유지 보수가 용이하다.
  • 여러 컴포넌트가 동일한 상태를 공유해야 할 때 유용하며, 실제로 업데이트가 필요한 컴포넌트만 리렌더링 하는 최적화도 가능하다.

 

 

 

 

 

 

17.1 작업 환경 설정

1. <  create-react-app > 으로 리액트 프로젝트 생성하기

  • $ yarn create react-app react-redux-tutorial

2. 리덕스와 react-redux 라이브러리를 설치
react-redux는 리덕스의 상태를 리액트 컴포넌트에 연결하는 라이브러리다.

  • $ cd react-redux-tutorial
  • $ yarn add redux react-redux

3. Prettier를 적용

코드 포맷팅 도구인 Prettier를 설정하여 일관된 코드 스타일을 유지한다.

// .prettierrc

{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80
}

 

 

 

 

 

17.2 UI 준비하기

리액트 프로젝트에서 리덕스를 사용할 때, 프레젠테이셔널 컴포넌트 컨테이너 컴포넌트를 분리하는 패턴을 가장 많이 사용한다. 이러한 패턴은 리덕스를 사용하는 데 필수 사항은 아니다. 다만 이 패턴을 사용하면 코드의 재사용성도 높아지고, 관심사의 분리가 이루어져 UI를 작성할 때 좀 더 집중할 수 있다.

 

 

  • 프레젠테이셔널 컴포넌트: 상태를 관리하지 않고 props만 받아 화면을 렌더링 하는 컴포넌트이다.
  • 컨테이너 컴포넌트: 리덕스와 연동되어 상태를 받아오고, 리덕스 스토어에 액션을 디스패치하는 컴포넌트이다.

 

 

 

 

 

  • src/components 경로 : UI에 관련된 프레젠테이셔널 컴포넌트 저장
  • src/containers 컴포넌트 : 리덕스와 연동된 컨테이너 컴포넌트 작성

 

 

 

17.2.1 카운터 컴포넌트 만들기

숫자를 더하거나 빼는 버튼을 가진 카운터 컴포넌트를 만들고, 이를 App 컴포넌트에서 사용한다.

// components/Counter.js

import React from 'react';

const Counter = ({ number, onIncrease, onDecrease }) => {
  return (
    <div>
      <h1>{number}</h1>
      <div>
        <button onClick={onIncrease}>+1</button>
        <button onClick={onDecrease}>-1</button>
      </div>
    </div>
  );
};

export default Counter;

 

// app.js

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

const App = () => {
  return (
    <div>
      <Counter number={0} />
    </div>
  );
};

export default App;

 

 

 

17.2.2 할 일 목록 컴포넌트 만들기

할 일을 추가하고, 체크하며, 삭제할 수 있는 할 일 목록 컴포넌트를 만든다.

// components/Todos.js

import React from 'react';

const TodoItem = ({ todo, onToggle, onRemove }) => {
  return (
    <div>
      <input type="checkbox" />
      <span>예제 텍스트</span>
      <button>삭제</button>
    </div>
  );
};

const Todos = ({
  input, // 인풋에 입력되는 텍스트
  todos, // 할 일 목록이 들어 있는 객체
  onChangeInput,
  onInsert,
  onToggle,
  onRemove,
}) => {
  const onSubmit = e => {
    e.preventDefault();
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input />
        <button type="submit">등록</button>
      </form>
      <div>
        <TodoItem />
        <TodoItem />
        <TodoItem />
        <TodoItem />
        <TodoItem />
      </div>
    </div>
  );
};

export default Todos;
// app.js

import React from 'react';
import Counter from './components/Counter';
import Todos from './components/Todos';

const App = () => {
  return (
    <div>
      <Counter number={0} />
      <hr />
      <Todos />
    </div>
  );
};

export default App;

 

 

 

 

17.3 리덕스 관련 코드 작성하기

리덕스를 사용할 때는 액션 타입, 액션 생성 함수, 리듀서 코드를 작성해야 하는데요. 이 코드들을 각각 다른 파일에 작성하는 방법도 있고, 기능별로 묶어서 파일 하나에 작성하는 방법도 있다.

 

 

 

 

각가의 다른 파일에 기능별로 파일을 하나씩 만드는 방식으로 코드를 종류에 따라 다른 파일에 작성하여 정리할 수 있습니다.

 

 

기능별로 하나에 몰아서 작성하는 방식으로 Ducks패턴이라고 부릅니다.

 

 

17.3.1 counter 모듈 작성하기

Ducks 패턴을 사용하여 액션 타입, 액션 생성 함수, 리듀서를 작성한 코드를 ‘모듈’이라고 한다.

 

  • counter 모듈: 숫자를 증가시키고 감소시키는 액션 타입과 액션 생성 함수를 정의하고, 리듀서에서는 상태를 변경하는 로직을 작성한다.
  • todos 모듈: 할 일 목록의 상태를 관리하는 액션 타입과 액션 생성 함수, 리듀서를 작성한다.

 

17.3.1.1 액션 타입 정의하기

// modules/counter.js 

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

 

액션타입은 대문자로 정의하고, 문자열 내용은 '모듈 이름/ 액션이름' 과 같은 형태로 작성한다. 

 

17.3.1.2 액션 생성 함수 만들기

// modules/counter.js 

export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

 

export 키워드를 사용하여 추후 다시 이 함수를 다른 파일에서 불러와 사용할 수 있도록 한다. 

 

17.3.1.3 초기 상태 및 리듀서 함수 만들기

// modules/counter.js 

const initialState = {
  number: 0
};

function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        number: state.number + 1
      };
    case DECREASE:
      return {
        number: state.number - 1
      };
    default:
      return state;
  }
}

export default counter;

 

counter모듈의 초기 상태와 리듀서 함수를 만들어 준다. 

초기상태에 number값을 설정해 주었으며, 리듀서 함수에는 현재상태를 참조하여 새로운 객체를 생성하여 반환하는 코드를 작서하였습니다.마지막으로 export default 키워드를 사용하여 함수를 내낸다.

 

( export는 여러 개를 내보낼 수 있지만 export default는 단 한 개만 내보낼 수 있다. )

 

 

 

 

 

 

17.3.2 todos 모듈 만들기

modules 디렉터리에 todos.js 파일을 생성

 

17.3.2.1 액션 타입 정의하기

// modules/todos.js

const CHANGE_INPUT = 'todos/CHANGE_INPUT'; // 인풋 값을 변경함
const INSERT = 'todos/INSERT'; // 새로운 todo를 등록함
const TOGGLE = 'todos/TOGGLE'; // todo를 체크/체크 해제함
const REMOVE = 'todos/REMOVE'; // todo를 제거함

 

17.3.2.2 액션 생성 함수 만들기

액션 생성 함수에서 파라미터가 필요하다. 전달받은 파라미터는 액션 객체 안에 추가 필드로 들어가게 된다.

// modules/todos.js

export const changeInput = input => ({
  type: CHANGE_INPUT,
  input
});

let id = 3; // insert가 호출될 때마다 1씩 더해집니다.
export const insert = text => ({
  type: INSERT,
  todo: {
    id: id++,
    text,
    done: false
  }
});

export const toggle = id => ({
  type: TOGGLE,
  id
});

export const remove = id => ({
  type: REMOVE,
  id
});

 

17.3.2.3 초기 상태 및 리듀서 함수 만들기

객체에 한 개 이상의 값이 들어가므로 불변성을 유지해 주어야 하기 때문에 spread 연산자()를 활용하여 작성한다.

// modules/todos.js

const initialState = {
  input: '',
  todos: [
    {
      id: 1,
      text: '리덕스 기초 배우기',
      done: true
    },
    {
      id: 2,
      text: '리액트와 리덕스 사용하기',
      done: false
    }
  ]
};

function todos(state = initialState, action) {
  switch (action.type) {
    case CHANGE_INPUT:
      return {
        ...state,
        input: action.input
      };
    case INSERT:
      return {
        ...state,
        todos: state.todos.concat(action.todo)
      };
    case TOGGLE:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.id ? { ...todo, done: !todo.done } : todo
        )
      };
    case REMOVE:
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id != = action.id)
      };
    default:
      return state;
  }
}

export default todos;

 

 

17.3.3 루트 리듀서 만들기

 

  • 여러 리듀서를 하나로 결합하여 createStore에서 사용할 수 있도록 combineReducers 함수를 사용해 통합한다.
  • 통합된 리듀서는 rootReducer로 내보낸다.

 

// modules/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos,
});

export default rootReducer;

 

이런 식으로 작성하면 나중에 불러올 때 디렉터리 이름만 장성하여 불러올 수 있다. 

 

 

 

 

 

 

 

 

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

< 리액트 애플리케이션에 리덕스를 적용 >

 

17.4.1 스토어 만들기

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import rootReducer from './modules';

const store = createStore(rootReducer);

ReactDOM.render(<App />, document.getElementById('root'));

serviceWorker.unregister();

 

 

 

17.4.2 Provider 컴포넌트를 사용하여 프로젝트에 리덕스 적용하기

리액트 컴포넌트에서 스토어를 사용할 수 있도록 App 컴포넌트를 react-redux에서 제공하는 Provider 컴포넌트로 감싸 준다.

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import rootReducer from './modules';

const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
);

serviceWorker.unregister();

 

 

17.4.3 Redux DevTools의 설치 및 적용

  • 크롬 웹 스토어(https://chrome.google.com/webstore/)에서 Redux DevTools를 검색하여 설치
    설치하고 나면 리덕스 스토어를 만드는 과정에서 다음과 같이 적용해 줄 수 있다.
const store = createStore(
  rootReducer, /* preloadedState, */
  window._ _REDUX_DEVTOOLS_EXTENSION_ _ && window._ _REDUX_DEVTOOLS_EXTENSION_ _()
);

 

  • 패키지 설치: $ yarn add redux-devtools-extension
// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import rootReducer from './modules';

const store = createStore(rootReducer, composeWithDevTools());

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
);

serviceWorker.unregister();

 

 

Redux  탭 - State  버튼 - 스토어 내부의 상태 확인

 

 

 

17.5 컨테이너 컴포넌트 만들기

< 컴포넌트에서 리덕스 스토어에 접근하여 원하는 상태를 받아 오고, 또 액션도 디스패치해 줄 차례 > 

리덕스 스토어와 연동된 컴포넌트를 컨테이너 컴포넌트라고 부른다.

 

17.5.1 CounterContainer 만들기

// src/containers/CounterContainer.js

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

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

export default CounterContainer;

 

 

리덕스와 연동하려면 react-redux에서 제공하는 connect 함수를 사용

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

mapStateToProps: 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수, state를 파라미터로 받아 오며, 이 값은 현재 스토어가 지니고 있는 상태를 가리킨다.

mapDispatchToProps: 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수, store의 내장 함수 dispatch를 파라미터로 받아 온다.

 

connect 함수를 호출하고 나면 또 다른 함수를 반환한다. 반환된 함수에 컴포넌트를 파라미터로 넣어 주면 리덕스와 연동된 컴포넌트가 만들어진다.

// src/containers/CounterContainer.js

import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';

const CounterContainer = ({ number, increase, decrease }) => {
  return (
    <Counter number={number} onIncrease={increase} onDecrease={decrease} />
  );
};

const mapStateToProps = state => ({
  number: state.counter.number,
});
const mapDispatchToProps = dispatch => ({
  // 임시 함수
  increase: () => {
    console.log('increase');
  },
  decrease: () => {
    console.log('decrease');
  },
});
export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(CounterContainer);

 

// app.js

import React from 'react';
import Todos from './components/Todos';
import CounterContainer from './containers/CounterContainer';

const App = () => {
  return (
    <div>
      <CounterContainer />
      <hr />
      <Todos />
    </div>
  );
};

export default App;

 

 

 

 

1. 액션 생성 함수를 불러와서 액션 객체를 만들고 디스패치

connect 함수를 사용할 때는 일반적으로 위 코드와 같이 mapStateToProps mapDispatchToProps를 미리 선언해 놓고 사용.

// components/CounterContainer.js

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} />
  );
};

const mapStateToProps = state => ({
  number: state.counter.number,
});
const mapDispatchToProps = dispatch => ({
  increase: () => {
    dispatch(increase());
  },
  decrease: () => {
    dispatch(decrease());
  },
});
export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(CounterContainer);

 

 

 

 

2. connect 함수 내부에 익명 함수 형태로 선언해도 문제가 되지 않는다.

액션함수의 개수가 많다면 리덕스에서 제공하는 bindActionCreators 유틸 함수를 사용하면 간편하다.

// containers/CounterContainer.js

import React from 'react';
import { bindActionCreators } from 'redux';
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(
  state => ({
    number: state.counter.number,
  }),
  dispatch =>
    bindActionCreators(
      {
        increase,
        decrease,
      },
      dispatch,
    ),
)(CounterContainer);

 

 

 

3. mapDispatchToProps에 해당하는 파라미터를 함수 형태가 아닌 액션 생성 함수로 이루어진 객체 형태로 넣어 준다.

// containers/CounterContainer.js

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(
  state => ({
    number: state.counter.number,
  }),
  {
    increase,
    decrease,
  },
)(CounterContainer);

 

위와 같이 두 번째 파라미터를 아예 객체 형태로 넣어 주면 connect 함수가 내부적으로 bindActionCreators 작업을 대신해 준다.

 

 

 

17.5.2 TodosContainer 만들기

< CounterContainer를 만들 때 배웠던 connect 함수를 사용하고, mapDispatchToProps를 짧고 간단하게 쓰는 방법을 적용해서 코드 >

// containers/TodosContainer 

import React from 'react';
import { connect } from 'react-redux';
import { changeInput, insert, toggle, remove } from '../modules/todos';
import Todos from '../components/Todos';

const TodosContainer = ({
  input,
  todos,
  changeInput,
  insert,
  toggle,
  remove,
}) => {
  return (
    <Todos
      input={input}
      todos={todos}
      onChangeInput={changeInput}
      onInsert={insert}
      onToggle={toggle}
      onRemove={remove}
    />
  );
};

export default connect(
  // 비구조화 할당을 통해 todos를 분리하여
  // state.todos.input 대신 todos.input을 사용
  ({ todos }) => ({
    input: todos.input,
    todos: todos.todos,
  }),
  {
    changeInput,
    insert,
    toggle,
    remove,
  },
)(TodosContainer);
// app.js

import React from 'react';
import CounterContainer from './containers/CounterContainer';
import TodosContainer from './containers/TodosContainer';

const App = () => {
  return (
    <div>
      <CounterContainer />
      <hr />
      <TodosContainer />
    </div>
  );
};

export default App;
// containers/Todos.js

import React from 'react';

const TodoItem = ({ todo, onToggle, onRemove }) => {
  return (
    <div>
      <input
        type="checkbox"
        onClick={() => onToggle(todo.id)}
        checked={todo.done}
        readOnly={true}
      />
      <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={() => onRemove(todo.id)}>삭제</button>
    </div>
  );
};

const Todos = ({
  input, // 인풋에 입력되는 텍스트
  todos, // 할 일 목록이 들어 있는 객체
  onChangeInput,
  onInsert,
  onToggle,
  onRemove,
}) => {
  const onSubmit = e => {
    e.preventDefault();
    onInsert(input);
    onChangeInput(''); // 등록 후 인풋 초기화
  };
  const onChange = e => onChangeInput(e.target.value);
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input value={input} onChange={onChange} />
        <button type="submit">등록</button>
      </form>
      <div>
        {todos.map(todo => (
          <TodoItem
            todo={todo}
            key={todo.id}
            onToggle={onToggle}
            onRemove={onRemove}
          />
        ))}
      </div>
    </div>
  );
};

export default Todos;

 

 

  • TodosContainer는 리덕스의 상태를 가져와서 프레젠테이셔널 컴포넌트인 Todos에 전달.
  • Todos는 할 일 목록을 관리하고 표시하는 컴포넌트로, 할 일을 추가, 토글, 삭제하는 기능을 구현.
  • App은 CounterContainer와 TodosContainer를 화면에 렌더링 하여, 카운터와 할 일 목록을 함께 보여준다.

 

 

 

 

 

17.6 리덕스 더 편하게 사용하기

17.6.1 redux-actions

redux-actions를 사용하면 액션 생성 함수를 더 짧은 코드로 작성할 수 있다.

 

 

  • $ yarn add redux-actions

17.6.1.1 counter 모듈에 적용하기

  • 액션 생성 함수: 액션 생성 함수를 createAction이란 함수를 사용하여 만든다.
    createAction을 사용하면 매번 객체를 직접 만들어 줄 필요 없이 더욱 간단하게 액션 생성 함수를 선언할 수 있다.
  • 리듀서: handleActions라는 함수를 사용하여  리듀서 함수도 더 간단하고 가독성 높게 작성한다.
// modules/counter.js 

import { createAction, handleActions } from 'redux-actions';

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

const initialState = {
  number: 0,
};

const counter = handleActions(
  {
    [INCREASE]: (state, action) => ({ number: state.number + 1 }),
    [DECREASE]: (state, action) => ({ number: state.number - 1 }),
  },
  initialState,
);

export default counter;

 

 

17.6.1.2 todos 모듈에 적용하기

션 생성 함수를 교체를 위해서는 각 액션 생성 함수에서 파라미터를 필요로 한다.

createAction으로 액션을 만들면 액션에 필요한 추가 데이터는 payload라는 이름을 사용한다.

const MY_ACTION = 'sample/MY_ACTION';
const myAction = createAction(MY_ACTION, text => `${text}!`);
const action = myAction('hello world');

 

 

 

 

 

 

 

17.6.2 immer

immer를 사용하면 상태를 불변성 유지하면서 더 쉽게 관리할 수 있다.

  • immer: 복잡한 객체 배열을 다룰 때 편리하게 상태를 업데이트.
  • $ yarn add immer

immer를 사용한다고 해서 모든 업데이트 함수에 immer를 적용할 필요는 없습니다. 일반 자바스크립트로 처리하는 것이 더 편할 때는 immer를 적용하지 않아도 된다.

 

 

 

 

 

 

 


17.7 Hooks를 사용하여 컨테이너 컴포넌트 만들기

connect 함수를 사용하는 대신 react-redux에서 제공하는 Hooks를 사용할 수도 있다.

 

17.7.1 useSelector로 상태 조회하기

  • const 결과 = useSelector(상태 선택 함수);
  • useSelector는 리덕스 상태를 컴포넌트에서 쉽게 조회할 수 있게 해주는 Hook이다.
  • useSelector를 사용하여 CounterContainer에서 counter.number를 조회하고, 이를 Counter 컴포넌트로 전달한다.

 

 

// containers/CounterContainer.js

import React from 'react';
import { useSelector } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';

const CounterContainer = () => {
  const number = useSelector(state => state.counter.number);
  return <Counter number={number} />;
};

export default CounterContainer;

 

 

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

  • const dispatch = useDispatch();
  • dispatch({ type: 'SAMPLE_ACTION' });
  • useDispatch는 액션을 디스패치할 수 있게 해주는 Hook이다.
  • onIncrease, onDecrease와 같은 액션을 디스패치하는 함수들을 useCallback으로 감싸서, 리렌더링을 최적화할 수 있다.
// containers/CounterContainer.js

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 number = useSelector(state => state.counter.number);
  const dispatch = useDispatch();
  const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
  const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);
  return (
    <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
  );
};

export default CounterContainer;

 

 

 

17.7.3 useStore를 사용하여 리덕스 스토어 사용하기

  • const store = useStore();
  • store.dispatch({ type: 'SAMPLE_ACTION '});
  • store.getState();

useStore는 리덕스 스토어에 직접 접근할 때 사용한다. 하지만 실제로 이런 상황은 드물기 때문에 일반적으로 useSelector와 useDispatch를 사용하는 것이 좋다.

 

 

17.7.4 TodosContainer를 Hooks로 전환하기

  • TodosContainer에서 useSelector와 useDispatch를 사용하여 상태를 관리하고, 액션들을 디스패치한다.
  • useCallback을 사용하여 불필요한 리렌더링을 방지한다.
// containers/TodosContainer.js

import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { changeInput, insert, toggle, remove } from '../modules/todos';
import Todos from '../components/Todos';

const TodosContainer = () => {
  const { input, todos } = useSelector(({ todos }) => ({
    input: todos.input,
    todos: todos.todos
  }));
  const dispatch = useDispatch();
  const onChangeInput = useCallback(input => dispatch(changeInput(input)), [
    dispatch
  ]);
  const onInsert = useCallback(text => dispatch(insert(text)), [dispatch]);
  const onToggle = useCallback(id => dispatch(toggle(id)), [dispatch]);
  const onRemove = useCallback(id => dispatch(remove(id)), [dispatch]);

  return (
    <Todos
      input={input}
      todos={todos}
      onChangeInput={onChangeInput}
      onInsert={onInsert}
      onToggle={onToggle}
      onRemove={onRemove}
    />
  );
};

export default TodosContainer;

 

 

17.7.5 useActions 유틸 Hook을 만들어서 사용하기

 참고 링크: https://react-redux.js.org/next/api/hooks#recipe-useactions

여러 액션을 한 번에 관리할 수 있도록 useActions라는 커스텀 Hook을 만들어서, 액션 생성 함수들을 디스패치 함수로 변환하여 코드의 중복을 줄인다.

 

 

 

17.7.6 connect 함수와의 주요 차이점

  • 컨테이너 생성 방법
    • connect 함수 또는 useSelector와 useDispatch를 사용.
    • 둘 중 편한 것을 선택하면 됨. connect는 여전히 사용 가능.
  • 차이점
    • connect는 부모 컴포넌트가 리렌더링 되더라도 props가 변경되지 않으면 리렌더링을 방지하는 성능 최적화가 자동으로 적용한다.
    • useSelector는 자동 최적화가 없기 때문에, 성능 최적화가 필요한 경우 React.memo를 사용해야 한다.
// containers/CounterContainer.js

import React from 'react';
import { useSelector } from 'react-redux';
import { changeInput, insert, toggle, remove } from '../modules/todos';
import Todos from '../components/Todos';
import useActions from '../lib/useActions';

const TodosContainer = () => {
  (...)
};

export default React.memo(TodosContainer);

 

 

 


17.8 정리

 

  • 리덕스를 사용하면 상태 관리와 관련된 로직을 리액트 컴포넌트와 분리할 수 있어, 유지보수가 용이한 코드를 작성할 수 있다.
  • 규모가 큰 프로젝트에서 리덕스를 사용하면 상태 관리가 더 체계적으로 이루어지고, 개발자 경험이 향상된다.

 


출처 :  김민준, 『리액트를 다루는 기술』, 길벗(2019).

Corner React.js 1

Editor: MARIN

 

728x90

관련글 더보기