개요
모던 자바스크립트의 구조를 학습하고 컴포넌트 기반의 코드를 작성한다.
목표
순수 Javascript로 작성된 Todo List 어플리케이션을 작성한다.
(이 글은 Next Step에서 진행한 js-todo-list-step을 따라 작성되었습니다.)
전체 코드는 여기에서 보실 수 있습니다.
여정
TodoCount 기본 뼈대 작성
TodoCount 컴포넌트는 3가지 기능을 가지고 있어야 한다.
- 필터링 : 3가지 필터를 설정할 수 있다(전체보기, 해야할 일, 완료한 일)
- 할 일 카운트 : 현재 표시된 할 일의 갯수를 표시한다
- 전체 삭제 : 할 일 전체를 삭제한다.
일단 컴포넌트 뼈대부터 잡으면 아래와 같다.
// TodoCount.js
import Component from "../core/Component.js";
import { store } from "../store/index.js";
export default class TodoCount extends Component {
initState() { return {}; }
mounted() {
// 컴포넌트가 마운트된 후에 동작한다.
}
template() {
return `
<span class="todo-count">
총 <strong>0</strong> 개
</span>
<ul class="filters">
<li>
<a href="#" class="all selected">전체보기</a>
</li>
<li>
<a href="#active" class="active">해야할 일</a>
</li>
<li>
<a href="#completed" class="completed">완료한 일</a>
</li>
</ul>
<button class="clear-completed">모두 삭제</button>
`;
}
setEvent() {
}
}
우선, 다른 기능에 영향을 미치는 필터링 기능을 만들어보자.
완료 체크박스 이벤트 구현
필터링 기능에 앞서,
우리는 완료 체크박스를 눌렀을 때 동작을 구현하지 않았다.
빠르게 작성한 결과만 보도록 하자.
// reducer.js
let initialState = {
users: [],
todos: [],
filter: FILTER_TYPE.ALL, // 추가
}
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
...
case CHANGE_FILTER:
return {
...state,
filter: action.payload.filter,
}
먼저, Reducer에 filter라는 상태를 추가하고 연산 정의해준다.
이어서 TodoList에 클릭 이벤트를 구현해주면 된다.
// TodoList.js
...
const TodoItem = (todo) => {
const { id, content, completed, } = todo;
return `
<li data-id="${id}" class="${completed ? 'completed' : ''}"> // class 추가
...
</li>
`;
}
export default class TodoList extends Component {
...
setEvent () {
this.onClickCompleteButton();
}
...
onClickCompleteButton() {
this.addEvent('click', '.toggle', (event) => {
const todoItem = this.getTargetTodoItem(event);
this.toggleTodoComplete(todoItem);
this.render();
})
}
toggleTodoComplete(todoItem) {
store.dispatch(toggleTodoComplete(todoItem.dataset.id));
}
...
기존에 작성했던 추가, 삭제 기능과 크게 다르지 않다.
이제 완료 체크박스를 클릭하면
할일 label에 취소선이 그어진다.
계속 진행해보자.
필터링 기능 구현
어려워보이지만, 차근차근 분리해서 구현하면 할만하다.
템플릿 함수부터 분리해서 구현해보자.
// TodoCount.js
template() {
const { todos, filter } = store.getState();
return `
...
<ul class="filters">
${renderFilterList(filter)}
</ul>
...
`;
}
앞서 filter를 구현했으니 store에서 불러올 수 있다.
이 필터값은 현재 선택된 필터를 나타낸다.
즉, 선택된 필터는 선택되었음을 표시하고 있어야 한다.
필터링 버튼을 그려주는 함수를 작성하여 한번 더 분리해보자.
// TodoCount.js
const renderFilterList = (filter) => {
return `
<li>
<a href="#" class="${setFilter(FILTER_TYPE.ALL, filter)}">전체보기</a>
</li>
<li>
<a href="#active" class="${setFilter(FILTER_TYPE.ACTIVE, filter)}">해야할 일</a>
</li>
<li>
<a href="#completed" class="${setFilter(FILTER_TYPE.COMPLETED, filter)}">완료한 일</a>
</li>
`;
}
const setFilter = (filterType, filter) => {
return filterType === filter ? filterType + ' selected' : filterType;
}
현재 filter 값에 따라서 필터 타입과 "selected" 클래스 속성을 부여해주고 있다.
이제 이벤트를 추가해보자.
// TodoCount.js
setEvent() {
this.onClickFilter();
}
onClickFilter() {
this.addEvent('click', '.filters a', (event) => {
const filter = event.target.className;
this.changeFilter(filter);
this.render();
})
}
changeFilter(filter) {
store.dispatch(changeFilter(filter));
}
클래스 속성을 가져와서 단순하게 store에 저장하고 있다.
하지만 아직 화면은 바뀌지 않는다.
할일 목록을 그려주는 TodoList에서 필터에 따라 그려주지 않았기 때문이다.
// TodoList.js
const filterTodoList = (todos, filter) => {
return todos && todos.filter((todo) => isFilteredTodo (filter, todo));
}
export default class TodoList extends Component {
...
template () {
const { todos, filter } = store.getState();
const filteredTodos = filterTodoList(todos, filter);
return `
<ul class="todo-list">
${renderTodoList(filteredTodos)}
</ul>
`;
}
// utils/filter.js
const isFilteredTodo = (filter, todo) => {
switch (filter) {
case FILTER_TYPE.ACTIVE:
return !todo.completed;
case FILTER_TYPE.COMPLETED:
return todo.completed;
case FILTER_TYPE.ALL:
return true;
}
}
필터값에 따라서 TodoList를 렌더링 하도록 변경하였다.
이제 실행해보면, 필터값에 따라 할일이 보여지는 것을 확인할 수 있다.
체크박스를 눌러도 잘 동작하는지도 확인하자.
자, 이제 카운트 기능과, 전체 삭제 기능이 남아있다.
희망적인 소식은 남은 두 기능은 아주 쉽다는 것이다.
카운트 기능 구현
카운트 기능 구현은 아래와 같이 구현할 수 있다.
// TodoCount.js
const renderTodoCount = (todos) => {
const count = todos && todos.filter(todo => isFilteredTodo (filter, todo)).length;
return '총 <strong>' + count + '</strong> 개';
}
export default class TodoCount extends Component {
...
template() {
const { todos, filter } = store.getState();
return `
<span class="todo-count">
${renderTodoCount(todos, filter)}
</span>
...
`;
}
TodoList에서 작성했던 것과 같이필터링된 목록의 갯수만 세어서 표시하면 된다.
전체삭제 기능 구현
전체 삭제 기능은
단순 클릭 이벤트만 구현하면 된다.
// TodoCount.js
setEvent() {
...
this.onClickDeleteAllButton();
}
...
onClickDeleteAllButton() {
this.addEvent('click', '.clear-completed', (event) => {
if(!confirm('정말 삭제하시겠습니까?')) { return; }
store.dispatch(deleteAllTodo());
})
}
Reducer도 간단하게 Todos만 초기화 시켜주면 된다.
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
...
case DELETE_ALL_TODO:
return {
...state,
todos: [],
}
실행시켜보면
처음 의도했던 3가지 기능이 정상적으로 동작하는 것을 볼 수 있다.
마무리
여기까지 오느라 정말 고생많았다.
기본적인 컴포넌트, Reducer 작성부터
User, Todo 컴포넌트 작성까지 초기 기능은 모두 구현했다.
다음 시간에는
User마다 Todo Item을 소유할 수 있도록 구조를 리팩토링해보자.
'Frontend' 카테고리의 다른 글
모던 자바스크립트 TodoList - Store 리팩토링 (0) | 2023.06.14 |
---|---|
모던 자바스크립트 TodoList - User별 TodoList (0) | 2023.05.21 |
모던 자바스크립트 TodoList - Todo Component (2) (0) | 2023.05.06 |
모던 자바스크립트 TodoList - Todo Component (1) (0) | 2023.05.02 |
모던 자바스크립트 TodoList - User Component (0) | 2023.04.21 |