Todo list를 통한 redux학습하기(4)

지난 포스팅에서는 Counter 앱을 만든 형식을 이용해서 새로운 todo추가가 가능한 todo list앱을 만들었다.

하지만 redux document에 나와있는 todo list에 비하면 아직 많이 부족한 부분이 있어서 이 부분을 보완하고 boot strap을 통해 간단한 디자인도 입혀보았다.

이전에 구현한 todo와 기능상 차이는 없지만 내부적으로 크게 바뀐 것은

  1. container component를 생성했다는 점과
  2. reducer를 여러 개로 분리할 수 있도록 combineReducer function을 사용했다는 점이다.

Todo와 TodoList, AddTodo component들은 이전의 react component를 상속받은 class형식에서 단순히 component를 반환하는 function으로 변경했다. 또한 각 속성들의 타입을 지정해주기 위해 propTypes를 정해주었다.

//components/TodoList.jsimport React from 'react';
import PropTypes from 'prop-types'

import Todo from './Todo';
import './TodoList.css';

const TodoList = ({ todos }) => (
<ul className='list-group container'>
{todos.map((todo, index) => (
<Todo key={index} {...todo} />
))}
</ul>
);

TodoList.propTypes = {
todos: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string.isRequired
}).isRequired
).isRequired,
};

export default TodoList;
//components/Todo.jsimport React, {Component} from 'react';
import PropTypes from 'prop-types';

const Todo = ({index, text}) => (
<li className='list-group-item row' data-item={index}>
<div className='col-sm text'>{text}</div>
<div className='col-sm complete'>
<button type="button" className="btn btn-outline-secondary">Complete</button>
</div>
<div className='col-sm delete'>
<button type="button" className="btn btn-outline-danger">Delete</button>
</div>
</li>
);

Todo.propTypes = {
text: PropTypes.string.isRequired
};

export default Todo;

Container component는 실제 컴포넌트가 모양을 보여질지를 정하는 Presentational component와 달리 데이터를 불러오고 상태를 변경하는 등 구체적으로 component가 어떻게 동작할지를 정하는 component이다.

가장 헤맸던 부분은 connect() 함수인데, Redux 내장 라이브러리인 connect()함수는 개발자가 subscribe() 함수를 통해 컴포넌트를 직접 렌더링시키지 않고 효율적으로 앱 구현을 가능하도록 하게 해준다고 한다. connect()는 mapStateToProps라는 함수를 정의해주어야 하는데, 이 함수는 state의 어떤 속성을 통해 presentational component의 모습을 변화시킬 지, state를 어떻게 수정할 지를 정해준다.

//containers/VisibleTodoListimport { connect } from 'react-redux';
import TodoList from '../components/TodoList';

const getVisibleTodos = (todos) => {
return todos;
};

const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos)
}
};

const VisibleTodoList = connect(mapStateToProps)(TodoList);

export default VisibleTodoList;

component이름이 VisibleTodoList인 이유는 향후 filtering기능을 추가할 것이기 때문이다. 현재는 필터링 없이 모든 todos를 렌더링시켜주면 되기 때문에 전달받은 state의 todos를 모두 리턴해주고 있다.

// Containers/AddTodo.jsimport React from 'react';
import { connect } from 'react-redux';

import { addTodo } from "../acitons/index";

let AddTodo = ({ dispatch }) => {
let input;

return (
<div>
<form id='addTodo' onSubmit={e => {
e.preventDefault();
dispatch(addTodo(input.value));
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
className='form-control'
id='inputTodo'
placeholder='Enter todo'
/>
<button className='btn btn-primary' type='submit'>ADD</button>
</form>
</div>
)
}

AddTodo = connect()(AddTodo);

export default AddTodo;

AddTodo는 presentational component와 container component의 속성 모두가 들어있는 상태이다. submit될 경우 input값이 addTodo()의 value가 되어 action을 만들어주고, 이를 store에 dispatch시킨다.

App component는 이 두 가지 container component를 모두 포함한 최종 component가 된다.

// components/App.jsimport React from 'react';

import AddTodo from '../containers/AddTodo';
import VisibleTodoList from '../containers/VisibleTodoList';

const App = () => (
<div>
<AddTodo/>
<VisibleTodoList/>
</div>
);

export default App;

App component를 index.js에 넘겨주고, index.js에서는 전달받은 App component를 Provider에 담아서 최종적으로 렌더링해준다. Provider는 각각의 container component들에 store를 넘겨주지 않더라도 모든 container component에서 store를 사용할 수 있게 해준다.

Github Source Code

woohyun

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store