[말로 풀어쓴 React] 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기2

DongGu·2021년 2월 3일
0

말로 풀어쓴 리액트

목록 보기
10/11

목차

17.1 작업환경 설정

17.2 UI 준비하기

  • 17.2.1 카운터 컴포넌트 만들기
  • 17.2.2 할 일 목록 컴포넌트 만들기

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

  • 17.3.1 counter 모듈 작성하기
    • 17.3.1.1 액션 타입 정의하기
    • 17.3.1.2 액션 생성 함수 만들기
    • 17.3.1.3 초기 상태 및 리듀서 함수 만들기
  • 17.3.2 todos 모듈 만들기
    • 17.3.2.1 액션 타입 정의하기
    • 17.3.2.2 액션 생성 함수 만들기
  • 17.3.3 루트 리듀서 만들기

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

  • 17.4.1 스토어 만들기
  • 17.4.2 Provider 컴포넌트를 사용해 프로젝트에 리덕스 적용하기
  • 17.4.3 Redux DevTools 설치 및 적용

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

  • 17.5.1 CounterContainer 만들기
    • 17.5.1.1 임시함수를 통해 connect로 컴포넌트와 리덕스 연결하기
    • 17.5.1.2 액션 생성 함수를 불러와 액션 객체를 만들고 디스패치해온 것의 connect 연결
    • 17.5.1.3 mapStateToProps, mapDispatchToProps의 익명함수화
    • 17.5.1.4 리덕스의 bindActionCreators 사용
  • 17.5.2 TodosContainer 만들기
    • 17.5.2.1 Todos 컴포넌트를 위한 컨테이너인 TodosContainer 작성
    • 17.5.2.2 App 컴포넌트 내 TodosContainer 적용
    • 17.5.2.3 Todos컴포넌트에서 받아온 props 사용하기

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

  • 17.6.1 redux-actions
    • 17.6.1.1 counter 모듈에 적용하기
    • 17.6.1.2 todos 모듈에 적용하기
  • 17.6.2 immer

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

  • 17.7.1 useSelector로 상태 조회하기
  • 17.7.2 useDispatch를 사용하여 액션 디스패치하기
  • 17.7.3 useStore를 사용하여 리덕스 스토어 사용하기
  • 17.7.4 TodosContainer를 Hooks로 전환하기
  • 17.7.5 useActions 유틸 Hook을 만들어서 사용하기
  • 17.7.6 connect 함수와 주요 차이점

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

컴포넌트에서 리덕스 스토어에 접근하여 원하는 상태를 받아오고, 액션도 디스패치해줄 차례이다. 리덕스 스토어와 연동된 컴포넌트를 컨테이너 컴포넌트라고 부른다.

17.5.1. CounterContainer 만들기

  • 17.5.1.1 임시함수를 통해 connect로 컴포넌트와 리덕스 연결하기
// containers/CounterContainer.js
import React from 'react';
import Counter from '../components/Counter';

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

export default CounterContainer

위 컴포넌트를 리덕스와 연동하려면, react-redux에서 제공하는 connect 함수를 사용해야 한다. connect 함수는 다음과 같이 사용한다.
connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)

여기서 mapStateToProps는 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수다. mapDispatchToProps는 액션 생성함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수이다.

더 구체화하면 const makeContainer = connect(mapStateToProps, mapDispatchToProps)makeContainer(타깃 컴포넌트) 로 적을 수 있다.

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

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

mapStateToProps와 mapDispatchProps에서 반환하는 객체 내부의 값들은 컴포넌트의 props로 전달된다. mapStateToProps는 state를 파라미터로 받아오며, 이 값은 현재 스토어가 지니고 있는 상태를 가리킨다.

mapDispatchToProps의 경우 store의 내장함수 dispatch를 파라미터로 받아온다. mapDispatchToProps에서는 진행 절차를 설명하기 위해 임시로 console.log를 사용하고 있다.

여기에 (CounterContainer)를 덧붙임으로써mapStateToProps, mapDispatchToProps를 CounterContainer과 연동한다.

App에서 Counter를 CounterContainer로 교체한다.

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;

  • 17.5.1.2 액션 생성 함수를 불러와 액션 객체를 만들고 디스패치해온 것의 connect 연결
// containers/CounterContainer.js
import React from 'react';
import {connect} from 'react-redux';
import Counter from '../components/Counter';
// 액션 생성함수 increase, decrease
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);


console.log()대신 액션 생성함수(import {increase, decrease} from '../module/counter')를 불러와서 액션 객체를 만들고 디스패치를 했다.

  • 디스패치?
  • 17.5.1.3 mapStateToProps, mapDispatchToProps의 익명함수화
//container/CounterContainer.js
import React from "react";
import { connect } from "react-redux";
import Counter from "../components/Counter";
// 액션 생성함수 increase, decrease
import { increase, decrease } from "../modules/counter";

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

export default connect(
  (state) => ({
    number: state.counter.number,
  }),
  (dispatch) => ({
    increase: () => dispatch(increase()),
    decrease: () => dispatch(decrease()),
  })
)(CounterContainer);
  • 17.5.1.4 리덕스의 bindActionCreators 사용
    컴포넌트에서 액션을 디스패치하기 위해 각 액션 생성 함수를 호출하고 dispatch로 감싸는 작업이 번거로울 수도 있다. 액션 생성 함수가 많을수록 번거롭다. 이럴 떄 리덕스의 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);
  • 17.5.1.5 mapDispatchToProps의 파라미터를 함수 형태가 아닌, '액션 생성 함수'로 이루어진 객체로 넣어주는 방법
    check 여기서 말하는 함수 형태인 파라미터가 뭔지
// 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 작업을 대신해준다.
check 여기서 말하는 두 번째 파라미터가 뭔지

17.5.2 TodosContainer 만들기

  • 17.5.2.1 Todos 컴포넌트를 위한 컨테이너인 TodosContainer 작성
    connect 함수, mapDispatchToProps를 짧고 간단하게 쓰는 방법을 적용해서 코드를 작성했다.
// container/TodosContainer.js
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}) => ({
    input: todos.input,
    todos: todos.todos,
  }),
  {
    changeInput,
    insert,
    toggle,
    remove,
  },
)(TodosContainer);

todos 모듈에서 작성했던 액션 생성함수와 상태 안에 있던 값을 컴포넌트의 props로 전달했다.
컨테이너 컴포넌트를 다 만든 후에 App 컴포넌트에서 보여주던 Todos 컴포넌트를 TodosContainer 컴포넌트로 교체했다.

  • 17.5.2.2 App 컴포넌트 내 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;
  • 17.5.2.3 Todos컴포넌트에서 받아온 props 사용하기
// components/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;   			
profile
코딩하는 신방과생

0개의 댓글