Skip to content

[10기 김장후] TodoList with CRUD #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: kimjanghu
Choose a base branch
from
13 changes: 13 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
env: {
browser: true,
node: true,
es2021: true,
},
plugins: ["prettier"],
extends: ["eslint:recommended", "plugin:prettier/recommended"],
parserOptions: {
ecmaVersion: 2021,
sourceType: "module",
},
};
125 changes: 125 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@

# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node

### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test
.env.production

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# End of https://www.toptal.com/developers/gitignore/api/node
8 changes: 8 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
singleQuote: true,
semi: true,
useTabs: false,
tabWidth: 2,
trailingComma: 'all',
printWidth: 120,
};
71 changes: 35 additions & 36 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>이벤트 - TODOS</title>
<link rel="stylesheet" href="./src/css/style.css" />
</head>
<body>
<div class="todoapp">
<h1>TODOS</h1>
<input
id="new-todo-title"
class="new-todo"
placeholder="할일을 추가해주세요"
autofocus
/>
<main>
<input class="toggle-all" type="checkbox" />
<ul id="todo-list" class="todo-list"></ul>
<div class="count-container">
<span class="todo-count">총 <strong>0</strong> 개</span>
<ul class="filters">
<li>
<a class="all selected" href="#">전체보기</a>
</li>
<li>
<a class="active" href="#active">해야할 일</a>
</li>
<li>
<a class="completed" href="#completed">완료한 일</a>
</li>
</ul>
</div>
</main>
</div>
</body>
</html>

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>이벤트 - TODOS</title>
<link rel="stylesheet" href="./src/css/style.css" />
<script type="module" src="./src/index.js" defer></script>
</head>

<body>
<div id="app" class="todoapp">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 프론트엔드 공부가 부족해서 그런데, 이런식으로 해서 바닐라 JS만으로도 JS 분리를 깔끔하게 되는 것 같아요.
좋은 경험 얻어 갑니다. 👍

<h1>TODOS</h1>
<input id="new-todo-title" class="new-todo" placeholder="할일을 추가해주세요" autofocus />
<main>
<input class="toggle-all" type="checkbox" />
<ul id="todo-list" class="todo-list"></ul>
<div class="count-container">
<span class="todo-count"></span>
<ul class="filters">
<li>
<a class="all selected" href="#">전체보기</a>
</li>
<li>
<a class="active" href="#active">해야할 일</a>
</li>
<li>
<a class="completed" href="#completed">완료한 일</a>
</li>
</ul>
</div>
</main>
</div>
</body>

</html>
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "js-todo-list-step1",
"version": "1.0.0",
"description": "<p align=\"middle\" > <img width=\"200px;\" src=\"./src/images/check_list.png\"/> </p> <h2 align=\"middle\">JS 투두리스트 스텝1</h2> <p align=\"middle\">자바스크립트로 구현 하는 투두리스트</p> <p align=\"middle\"> <img src=\"https://img.shields.io/badge/version-1.0.0-blue?style=flat-square\" alt=\"template version\"/> <img src=\"https://img.shields.io/badge/language-html-red.svg?style=flat-square\"/> <img src=\"https://img.shields.io/badge/language-css-blue.svg?style=flat-square\"/> <img src=\"https://img.shields.io/badge/language-js-yellow.svg?style=flat-square\"/> <a href=\"https://github.com/next-step/js-todo-list-step1/blob/main/LICENSE\" target=\"_blank\"> <img src=\"https://img.shields.io/github/license/next-step/js-todo-list-step1.svg?style=flat-square&label=license&color=08CE5D\"/> </a> </p>",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kimjanghu/js-todo-list-step1.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/kimjanghu/js-todo-list-step1/issues"
},
"homepage": "https://github.com/kimjanghu/js-todo-list-step1#readme",
"devDependencies": {
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"prettier": "^2.3.2"
}
}
108 changes: 108 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import NewTodoInput from './components/NewTodoInput.js';
import TodoCount from './components/TodoCount.js';
import TodoList from './components/TodoList.js';
import Component from './core/component.js';
import State from './core/State.js';
import { FILTER_TYPES, STORAGE_KEY } from './utils/constants.js';
import { $, setLocalStorageItem, getLocalStorageItem } from './utils/utils.js';

export default class App extends Component {
setState() {
this.todoList = new State(getLocalStorageItem(STORAGE_KEY.TODO) || []);
this.filteredTodoList = new State(null);
this.filterState = new State('all');
}

render() {
this.mountChildren();
this.mountTodoList();
}

setFilteredTodoList() {
const filterTodo = Object.freeze({
[FILTER_TYPES.ALL]: this.viewAllTodo.bind(this),
[FILTER_TYPES.ACTIVE]: this.viewActiveTodo.bind(this),
[FILTER_TYPES.COMPLETED]: this.viewCompletedTodo.bind(this),
});
filterTodo[this.filterState.get()]();
// this.todoListView.setState(this.filteredTodoList.get());
// this.todoListView.render();
this.renderComponent(this.todoListView);
this.renderComponent(this.todoCountView);
this.todoCountView.setState(this.filteredTodoList.get());
this.todoCountView.render();
}

renderComponent(view) {
view.setState(this.filteredTodoList.get());
view.render();
}

viewAllTodo() {
this.filteredTodoList.set(this.todoList.get());
}

viewActiveTodo() {
const activeTodo = this.todoList.get().filter((todo) => todo.checked === false);
this.filteredTodoList.set(activeTodo);
}

viewCompletedTodo() {
const completedTodo = this.todoList.get().filter((todo) => todo.checked === true);
this.filteredTodoList.set(completedTodo);
}

mountChildren() {
new NewTodoInput($('#new-todo-title'), {
todoList: this.todoList,
onSubmitTodo: this.mountTodoList.bind(this),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ 이렇게 바꾼 이유는 혹시 onSubmitTodo를 이 App.js 내부에서는 그려짐과 동시에 행해야 할 것이기에 명칭을 바꾼 것으로 생각됩니다만, key값은 그대로 두신 이유가 궁금해졌습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props를 받는 컴포넌트의 네임을 바꾸기 귀찮아서 그대로 뒀습니다 ㅎㅎ 큰 이유는 없었어요

});
}

mountTodoList() {
this.todoListView = new TodoList($('#todo-list'), {
todoList: this.filteredTodoList.get() === null ? this.todoList.get() : this.filteredTodoList.get(),
checkTodo: this.checkTodo.bind(this),
deleteTodo: this.deleteTodo.bind(this),
editTodo: this.editTodo.bind(this),
});

this.todoCountView = new TodoCount($('.count-container'), {
todoList: this.filteredTodoList.get() === null ? this.todoList.get() : this.filteredTodoList.get(),
Copy link

@JamieShin0201 JamieShin0201 Jul 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todoList: this.filteredTodoList.get() ?? this.todoList.get(),

Nullish coalescing operator를 이용해 볼 수도 있을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다! 한번 찾아보고 적용해보겠습니다!

filterState: this.filterState,
onClickTodoRender: this.setFilteredTodoList.bind(this),
});
}

checkTodo(id) {
const todoList = this.todoList.get().map((todo) => {
if (todo.id === id) {
todo.checked = !todo.checked;
return todo;
}
return todo;
});
this.todoList.set([...todoList]);
setLocalStorageItem(STORAGE_KEY, todoList);
}

deleteTodo(id) {
const todoList = this.todoList.get().filter((todo) => todo.id !== id);
setLocalStorageItem(STORAGE_KEY.TODO, todoList);
this.todoList.set([...todoList]);
this.todoCountView.render();
}

editTodo(id, value) {
const todoList = this.todoList.get().map((todo) => {
if (todo.id === id) {
todo.todo = value;
return todo;
}
return todo;
Comment on lines +98 to +102

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return을 두 번 작성하신 건 가독성 때문일까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 어차피 todo.id와 id가 같은지 다른지 2가지 경우가 생기기 때문에 저런식으로 작성했습니다.

});
setLocalStorageItem(STORAGE_KEY.TODO, todoList);
this.todoList.set([...todoList]);
this.todoListView.render();
}
}
25 changes: 25 additions & 0 deletions src/components/NewTodoInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Component from '../core/component.js';
import { STORAGE_KEY } from '../utils/constants.js';
import { setLocalStorageItem } from '../utils/utils.js';

export default class NewTodoInput extends Component {
bindEvents() {
this.$target.addEventListener('keyup', ({ key }) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(질문) 여기서 ({ key }) 이렇게 하는 것과 ( key ) 이렇게 하는 것은 차이가 없겠지요?
기초적일 수 있지만 여쭈어 보아요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 destructuring 키워드를 찾아보시면 될 것같아요! 차이는 없지만 전 가독성 측면에서 destructuring을 즐겨써요

if (key !== 'Enter') return;
this.addTodo(this.$target.value);
this.$target.value = '';
this.props.onSubmitTodo();
});
}

addTodo(todo) {
const todoList = this.props.todoList.get();
todoList.push({
id: Date.now(),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인의 todolist로 사용하는 todo프로젝트이니 now로도 id가 중복될 일은 없겠네요!

todo,
checked: false,
});
this.props.todoList.set(todoList);
Comment on lines +17 to +22
Copy link

@JamieShin0201 JamieShin0201 Jul 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newTodoList = [...todoList, {
        id: Date.now(),
        todo,
        checked: false,
   }];

push로 배열의 상태를 바꾸는게 나을지 펼침연산자 이용해 새로운 리스트를 만드는게 좋을지 고민이 되네요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

펼침연산자를 사용하는게 더 좋아보입니다! (알고리즘을 제외하고) 최대한 원본배열을 건들지않고 작업하는게 유지보수나 디버깅 측면에서 유리하다고 들었습니다. 좋은 케이스 감사합니다.

setLocalStorageItem(STORAGE_KEY.TODO, todoList);
}
}
Loading