From 71a9858eec2b253f6f316d99bdabe459be58ef00 Mon Sep 17 00:00:00 2001 From: yejinleee <81412212+yejinleee@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:56:49 +0900 Subject: [PATCH 1/5] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e7140d8..a11f31d 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,8 @@ ## ⚙️ 컨벤션 * 본인 이름의 브랜치에 정리 파일 및 실습 파일 올리고 PR 보내기 -* 파일명 규칙 - * 본인 이름 폴더 / 챕터 {번호 및 제목} / {정리}.md - * 본인 이름 폴더 / 챕터 {번호 및 제목} / Practice / {문제}.ts +* 파일업로드 + * `본인 이름 폴더` / 내에 정리 md 파일, 예제문제 풀이 ts파일
@@ -43,5 +42,6 @@ | 회차 | 일시 | 스터디 내용 | | ---- | -------- | -------- | | 1 | 23.11.13 월 | CHAPTER 1, 2, 3, 4 | +| 2 | 23.11.20 월 | CHAPTER 5, 6, 7 ,10 | From a1cc1737ad89709d70dd22414da132b55718b919 Mon Sep 17 00:00:00 2001 From: yejinleee <81412212+yejinleee@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:25:23 +0900 Subject: [PATCH 2/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a11f31d..0344022 100644 --- a/README.md +++ b/README.md @@ -43,5 +43,6 @@ | ---- | -------- | -------- | | 1 | 23.11.13 월 | CHAPTER 1, 2, 3, 4 | | 2 | 23.11.20 월 | CHAPTER 5, 6, 7 ,10 | +| 3 | 23.11.27 월 | CHAPTER 8,9 + type-challenges 워밍업(1) & 쉬움(13) | From 904462d17f198e04452805a4b8635f4d10b0ca76 Mon Sep 17 00:00:00 2001 From: yejinleee <81412212+yejinleee@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:29:29 +0900 Subject: [PATCH 3/5] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0344022..71dffce 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ ## ⏰ 스터디 시간 -**매주 월요일 10:00** +~~매주 월요일 10:00~~ + +**월요일 16:00**
@@ -44,5 +46,7 @@ | 1 | 23.11.13 월 | CHAPTER 1, 2, 3, 4 | | 2 | 23.11.20 월 | CHAPTER 5, 6, 7 ,10 | | 3 | 23.11.27 월 | CHAPTER 8,9 + type-challenges 워밍업(1) & 쉬움(13) | +| 4 | 23.12.04 월 | CHAPTER 15, Vue+TS 과제 중 이슈 공유 | +| 5 | 23.12.11 월 | TodoList 과제 TS 전환 | From 473d0eccf301ba47297684662a62059671aee566 Mon Sep 17 00:00:00 2001 From: HoberMin <102784200+HoberMin@users.noreply.github.com> Date: Sat, 9 Dec 2023 00:23:38 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Todo=20TS=EA=B3=BC=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + index.html | 16 +++++++ package-lock.json | 29 ++++++++++++ package.json | 15 ++++++ src/App.js | 45 ++++++++++++++++++ src/App.ts | 57 +++++++++++++++++++++++ src/components/Header.js | 12 +++++ src/components/Header.ts | 19 ++++++++ src/components/TodoCounter.js | 18 ++++++++ src/components/TodoCounter.ts | 27 +++++++++++ src/components/TodoList.js | 63 +++++++++++++++++++++++++ src/components/TodoList.ts | 76 +++++++++++++++++++++++++++++++ src/components/createTodoInput.js | 24 ++++++++++ src/components/createTodoInput.ts | 34 ++++++++++++++ src/main.js | 11 +++++ src/main.ts | 16 +++++++ src/util/storage.js | 13 ++++++ src/util/storage.ts | 14 ++++++ src/util/types.js | 1 + src/util/types.ts | 31 +++++++++++++ tsconfig.json | 10 ++++ 21 files changed, 532 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/App.js create mode 100644 src/App.ts create mode 100644 src/components/Header.js create mode 100644 src/components/Header.ts create mode 100644 src/components/TodoCounter.js create mode 100644 src/components/TodoCounter.ts create mode 100644 src/components/TodoList.js create mode 100644 src/components/TodoList.ts create mode 100644 src/components/createTodoInput.js create mode 100644 src/components/createTodoInput.ts create mode 100644 src/main.js create mode 100644 src/main.ts create mode 100644 src/util/storage.js create mode 100644 src/util/storage.ts create mode 100644 src/util/types.js create mode 100644 src/util/types.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..3e44d06 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + h0ber Ts + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c990831 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "fedc5_learningts_study", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fedc5_learningts_study", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "typescript": "^5.3.3" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1d1213f --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "fedc5_learningts_study", + "version": "1.0.0", + "description": "[러닝 타입스크립트](https://www.yes24.com/Product/Goods/116585556)로 타입스크립트 뿌시기 !", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "typescript": "^5.3.3" + } +} diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..8541c44 --- /dev/null +++ b/src/App.js @@ -0,0 +1,45 @@ +import Header from './components/Header.js'; +import createTodo from './components/createTodoInput.js'; +import TodoList from './components/TodoList.js'; +import TodoCounter from './components/TodoCounter.js'; +const HEADER_TITLE = 'TODO LIST'; +export default class App { + constructor({ $app, initialState }) { + this.$app = $app; + this.state = initialState; + new Header({ + $app: this.$app, + title: HEADER_TITLE, + }); + new createTodo({ + $app: this.$app, + onSubmit: (text) => { + const nextState = this.makeNextState(text); + this.state = nextState; + todoList.setState(nextState); + }, + }); + const todoList = new TodoList({ + $app: this.$app, + todoInitialState: this.state, + updateTodoCounter: (nextState) => { + todoCounter.setState(nextState); + }, + }); + const todoCounter = new TodoCounter({ + $app: this.$app, + initialState: this.state, + }); + } + makeNextState(text) { + const nextState = [ + ...this.state, + { + isCompleted: false, + title: text, + id: new Date().getTime().toString(), + }, + ]; + return nextState; + } +} diff --git a/src/App.ts b/src/App.ts new file mode 100644 index 0000000..bb69132 --- /dev/null +++ b/src/App.ts @@ -0,0 +1,57 @@ +import { Todo, AppProps } from './util/types.js'; + +import Header from './components/Header.js'; +import createTodo from './components/createTodoInput.js'; +import TodoList from './components/TodoList.js'; +import TodoCounter from './components/TodoCounter.js'; + +const HEADER_TITLE = 'TODO LIST'; + +export default class App { + $app: HTMLDivElement; + state: Todo[]; + + constructor({ $app, initialState }: AppProps) { + this.$app = $app; + this.state = initialState; + + new Header({ + $app: this.$app, + title: HEADER_TITLE, + }); + + new createTodo({ + $app: this.$app, + onSubmit: (text: string) => { + const nextState = this.makeNextState(text); + this.state = nextState; + todoList.setState(nextState); + }, + }); + + const todoList = new TodoList({ + $app: this.$app, + todoInitialState: this.state, + updateTodoCounter: (nextState: Todo[]) => { + todoCounter.setState(nextState); + }, + }); + + const todoCounter = new TodoCounter({ + $app: this.$app, + initialState: this.state, + }); + } + + makeNextState(text: string): Todo[] { + const nextState = [ + ...this.state, + { + isCompleted: false, + title: text, + id: new Date().getTime().toString(), + }, + ]; + return nextState; + } +} diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 0000000..98fd2ad --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,12 @@ +export default class Header { + constructor({ $app, title }) { + this.$header = document.createElement('h1'); + this.$app = $app; + this.title = title; + this.render(); + } + render() { + this.$header.textContent = this.title; + this.$app.appendChild(this.$header); + } +} diff --git a/src/components/Header.ts b/src/components/Header.ts new file mode 100644 index 0000000..349362a --- /dev/null +++ b/src/components/Header.ts @@ -0,0 +1,19 @@ +import { HeaderProps } from '../util/types.js'; + +export default class Header { + $app: HTMLDivElement; + title: string; + $header: HTMLHeadElement; + + constructor({ $app, title }: HeaderProps) { + this.$header = document.createElement('h1'); + this.$app = $app; + this.title = title; + this.render(); + } + + render(): void { + this.$header.textContent = this.title; + this.$app.appendChild(this.$header); + } +} diff --git a/src/components/TodoCounter.js b/src/components/TodoCounter.js new file mode 100644 index 0000000..148d6bc --- /dev/null +++ b/src/components/TodoCounter.js @@ -0,0 +1,18 @@ +export default class TodoCounter { + constructor({ $app, initialState }) { + this.$counter = document.createElement('div'); + $app.appendChild(this.$counter); + this.state = initialState; + this.render(); + } + setState(nextState) { + this.state = nextState; + this.render(); + } + render() { + this.$counter.innerHTML = ` +
Total: ${this.state.length}
+
Done: ${this.state.filter((e) => e.isCompleted).length}
+ `; + } +} diff --git a/src/components/TodoCounter.ts b/src/components/TodoCounter.ts new file mode 100644 index 0000000..706e36e --- /dev/null +++ b/src/components/TodoCounter.ts @@ -0,0 +1,27 @@ +import { Todo, TodoCounterProps } from '../util/types.js'; + +export default class TodoCounter { + state: Todo[]; + $counter: HTMLDivElement; + + constructor({ $app, initialState }: TodoCounterProps) { + this.$counter = document.createElement('div'); + $app.appendChild(this.$counter); + this.state = initialState; + this.render(); + } + + setState(nextState: Todo[]): void { + this.state = nextState; + this.render(); + } + + render(): void { + this.$counter.innerHTML = ` +
Total: ${this.state.length}
+
Done: ${ + this.state.filter((e) => e.isCompleted).length + }
+ `; + } +} diff --git a/src/components/TodoList.js b/src/components/TodoList.js new file mode 100644 index 0000000..a67a84d --- /dev/null +++ b/src/components/TodoList.js @@ -0,0 +1,63 @@ +import { setItem } from '../util/storage.js'; +const STORAGE_KEY = 'todo'; +export default class TodoList { + constructor({ $app, todoInitialState, updateTodoCounter }) { + this.$todoList = document.createElement('div'); + $app.appendChild(this.$todoList); + this.updateTodoCounter = updateTodoCounter; + this.state = todoInitialState; + this.setEvent(); + this.render(); + } + completedTodo(id) { + const nextState = this.state.map((todo) => { + if (todo.id === id) { + return { + ...todo, + isCompleted: !todo.isCompleted, + }; + } + return todo; + }); + this.setState(nextState); + } + removeTodo(id) { + const nextState = this.state.filter((todo) => todo.id !== id); + this.setState(nextState); + } + setState(nextState) { + setItem(STORAGE_KEY, JSON.stringify(nextState)); + this.updateTodoCounter(nextState); + this.state = nextState; + this.render(); + } + render() { + this.$todoList.innerHTML = ` + + `; + } + setEvent() { + this.$todoList.addEventListener('click', (event) => { + const target = event.target; + const id = target.dataset.id; + if (id !== null) { + target.className === 'toggled-text' && this.completedTodo(id); + target.className === 'remove-button' && this.removeTodo(id); + } + }); + } +} diff --git a/src/components/TodoList.ts b/src/components/TodoList.ts new file mode 100644 index 0000000..7617d05 --- /dev/null +++ b/src/components/TodoList.ts @@ -0,0 +1,76 @@ +import { setItem } from '../util/storage.js'; +import { TodoListProps, Todo } from '../util/types.js'; + +const STORAGE_KEY = 'todo'; + +export default class TodoList { + $todoList: HTMLDivElement; + state: Todo[]; + updateTodoCounter: (nextState: Todo[]) => void; + + constructor({ $app, todoInitialState, updateTodoCounter }: TodoListProps) { + this.$todoList = document.createElement('div'); + $app.appendChild(this.$todoList); + + this.updateTodoCounter = updateTodoCounter; + this.state = todoInitialState; + this.setEvent(); + this.render(); + } + + completedTodo(id: string): void { + const nextState = this.state.map((todo) => { + if (todo.id === id) { + return { + ...todo, + isCompleted: !todo.isCompleted, + }; + } + return todo; + }); + this.setState(nextState); + } + + removeTodo(id: string): void { + const nextState = this.state.filter((todo) => todo.id !== id); + this.setState(nextState); + } + + setState(nextState: Todo[]): void { + setItem(STORAGE_KEY, JSON.stringify(nextState)); + this.updateTodoCounter(nextState); + this.state = nextState; + this.render(); + } + + render(): void { + this.$todoList.innerHTML = ` + + `; + } + + setEvent(): void { + this.$todoList.addEventListener('click', (event) => { + const target = event.target as HTMLElement; + const id = target.dataset.id as string | null; + if (id !== null) { + target.className === 'toggled-text' && this.completedTodo(id); + target.className === 'remove-button' && this.removeTodo(id); + } + }); + } +} diff --git a/src/components/createTodoInput.js b/src/components/createTodoInput.js new file mode 100644 index 0000000..56b0366 --- /dev/null +++ b/src/components/createTodoInput.js @@ -0,0 +1,24 @@ +export default class createTodo { + constructor({ $app, onSubmit }) { + this.$form = document.createElement('form'); + $app.appendChild(this.$form); + this.isInit = false; + this.render(); + this.onSubmit = onSubmit; + } + render() { + this.$form.innerHTML = ` + + + `; + if (!this.isInit) { + this.$form.addEventListener('submit', (e) => { + e.preventDefault(); + const $input = this.$form.querySelector('input[name="todo"]'); + const text = $input.value; + this.onSubmit(text); + $input.value = ''; + }); + } + } +} diff --git a/src/components/createTodoInput.ts b/src/components/createTodoInput.ts new file mode 100644 index 0000000..e628752 --- /dev/null +++ b/src/components/createTodoInput.ts @@ -0,0 +1,34 @@ +import { createTodoProps } from '../util/types.js'; + +export default class createTodo { + $form: HTMLFormElement; + isInit: boolean; + onSubmit: (text: string) => void; + + constructor({ $app, onSubmit }: createTodoProps) { + this.$form = document.createElement('form'); + $app.appendChild(this.$form); + this.isInit = false; + this.render(); + this.onSubmit = onSubmit; + } + + render(): void { + this.$form.innerHTML = ` + + + `; + + if (!this.isInit) { + this.$form.addEventListener('submit', (e) => { + e.preventDefault(); + const $input = this.$form.querySelector( + 'input[name="todo"]', + ) as HTMLInputElement; + const text = $input.value; + this.onSubmit(text); + $input.value = ''; + }); + } + } +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..a2f872e --- /dev/null +++ b/src/main.js @@ -0,0 +1,11 @@ +import App from './App.js'; +import { getItem } from './util/storage.js'; +const STORAGE_KEY = 'todo'; +const $app = document.querySelector('#app'); +const todoInitialState = getItem(STORAGE_KEY, []); +// 여기서 as Todo[]를 안해도 에러가 발생하지 않지만 getItem의 반환값이 Todo[]라는 것을 +// 명시적으로 알려주기 위해 as Todo[]를 사용했습니다. +new App({ + $app, + initialState: todoInitialState, +}); diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..aed834e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,16 @@ +import App from './App.js'; +import { getItem } from './util/storage.js'; + +const STORAGE_KEY = 'todo'; + +const $app = document.querySelector('#app')!; + +const todoInitialState = getItem(STORAGE_KEY, []); + +// 여기서 as Todo[]를 안해도 에러가 발생하지 않지만 getItem의 반환값이 Todo[]라는 것을 +// 명시적으로 알려주기 위해 as Todo[]를 사용했습니다. + +new App({ + $app, + initialState: todoInitialState, +}); diff --git a/src/util/storage.js b/src/util/storage.js new file mode 100644 index 0000000..cbd7e7b --- /dev/null +++ b/src/util/storage.js @@ -0,0 +1,13 @@ +const storage = window.localStorage; +export const setItem = (key, value) => { + try { + storage.setItem(key, value); + } + catch (e) { + console.log(e); + } +}; +export const getItem = (key, defaultValue) => { + const storedValue = storage.getItem(key); + return storedValue ? JSON.parse(storedValue) : defaultValue; +}; diff --git a/src/util/storage.ts b/src/util/storage.ts new file mode 100644 index 0000000..9b0631d --- /dev/null +++ b/src/util/storage.ts @@ -0,0 +1,14 @@ +const storage = window.localStorage; + +export const setItem = (key: string, value: string) => { + try { + storage.setItem(key, value); + } catch (e) { + console.log(e); + } +}; + +export const getItem = (key: string, defaultValue: []) => { + const storedValue = storage.getItem(key); + return storedValue ? JSON.parse(storedValue) : defaultValue; +}; diff --git a/src/util/types.js b/src/util/types.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/util/types.js @@ -0,0 +1 @@ +export {}; diff --git a/src/util/types.ts b/src/util/types.ts new file mode 100644 index 0000000..3641f5f --- /dev/null +++ b/src/util/types.ts @@ -0,0 +1,31 @@ +export interface Todo { + isCompleted: boolean; + id: string; + title: string; +} + +export interface AppProps { + $app: HTMLDivElement; + initialState: Todo[]; +} + +export interface HeaderProps { + $app: HTMLDivElement; + title: string; +} + +export interface createTodoProps { + $app: HTMLDivElement; + onSubmit: (text: string) => void; +} + +export interface TodoListProps { + $app: HTMLElement; + todoInitialState: Todo[]; + updateTodoCounter: (nextState: Todo[]) => void; +} + +export interface TodoCounterProps { + $app: HTMLDivElement; + initialState: Todo[]; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2ee9328 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "es2020", + "strict": true, + "esModuleInterop": true, + "strictPropertyInitialization": false, + "moduleResolution": "node" + } +} From d6b0e3369ef6b48d524892eb915e41305071c1ed Mon Sep 17 00:00:00 2001 From: HoberMin <102784200+HoberMin@users.noreply.github.com> Date: Thu, 14 Dec 2023 02:26:36 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refector=20:=20storage=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=A7=80=EC=A0=95,=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20any=ED=83=80=EC=9E=85=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 49 +++++++++++++++++++-------- src/App.ts | 55 ++++++++++++++++++++++--------- src/components/Header.ts | 6 ++-- src/components/TodoCounter.ts | 4 +-- src/components/TodoList.js | 28 ++++------------ src/components/TodoList.ts | 48 +++++++++++---------------- src/components/createTodoInput.js | 8 +++-- src/components/createTodoInput.ts | 21 +++++++----- src/main.js | 6 ++-- src/main.ts | 8 ++--- src/util/storage.js | 8 ++++- src/util/storage.ts | 13 ++++++-- src/util/types.ts | 27 ++++++++------- 13 files changed, 162 insertions(+), 119 deletions(-) diff --git a/src/App.js b/src/App.js index 8541c44..f4c008a 100644 --- a/src/App.js +++ b/src/App.js @@ -14,32 +14,55 @@ export default class App { new createTodo({ $app: this.$app, onSubmit: (text) => { - const nextState = this.makeNextState(text); + const nextState = makeNextState(text); this.state = nextState; todoList.setState(nextState); + todoCounter.setState(nextState); }, }); const todoList = new TodoList({ $app: this.$app, - todoInitialState: this.state, + initialState: this.state, updateTodoCounter: (nextState) => { todoCounter.setState(nextState); }, + onRemoveTodo: (id) => { + const nextState = this.state.filter((todo) => todo.id !== id); + this.state = nextState; + todoList.setState(nextState); + todoCounter.setState(nextState); + }, + onToggleTodo: (id) => { + const nextState = this.state.map((todo) => { + if (todo.id === id) { + return { + ...todo, + isCompleted: !todo.isCompleted, + }; + } + return todo; + }); + this.state = nextState; + todoList.setState(nextState); + todoCounter.setState(nextState); + }, }); const todoCounter = new TodoCounter({ $app: this.$app, initialState: this.state, }); - } - makeNextState(text) { - const nextState = [ - ...this.state, - { - isCompleted: false, - title: text, - id: new Date().getTime().toString(), - }, - ]; - return nextState; + const makeNextState = (text) => { + console.log(this.state); + const nextState = [ + ...this.state, + { + isCompleted: false, + title: text, + id: new Date().getTime().toString(), + }, + ]; + this.state = nextState; + return nextState; + }; } } diff --git a/src/App.ts b/src/App.ts index bb69132..827ebce 100644 --- a/src/App.ts +++ b/src/App.ts @@ -8,8 +8,8 @@ import TodoCounter from './components/TodoCounter.js'; const HEADER_TITLE = 'TODO LIST'; export default class App { - $app: HTMLDivElement; - state: Todo[]; + private readonly $app: HTMLDivElement; + private state: Todo[]; constructor({ $app, initialState }: AppProps) { this.$app = $app; @@ -23,35 +23,60 @@ export default class App { new createTodo({ $app: this.$app, onSubmit: (text: string) => { - const nextState = this.makeNextState(text); + const nextState = makeNextState(text); this.state = nextState; todoList.setState(nextState); + todoCounter.setState(nextState); }, }); const todoList = new TodoList({ $app: this.$app, - todoInitialState: this.state, + initialState: this.state, updateTodoCounter: (nextState: Todo[]) => { todoCounter.setState(nextState); }, + + onRemoveTodo: (id: string) => { + const nextState = this.state.filter((todo) => todo.id !== id); + this.state = nextState; + todoList.setState(nextState); + todoCounter.setState(nextState); + }, + + onToggleTodo: (id: string) => { + const nextState = this.state.map((todo) => { + if (todo.id === id) { + return { + ...todo, + isCompleted: !todo.isCompleted, + }; + } + return todo; + }); + this.state = nextState; + todoList.setState(nextState); + todoCounter.setState(nextState); + }, }); const todoCounter = new TodoCounter({ $app: this.$app, initialState: this.state, }); - } - makeNextState(text: string): Todo[] { - const nextState = [ - ...this.state, - { - isCompleted: false, - title: text, - id: new Date().getTime().toString(), - }, - ]; - return nextState; + const makeNextState = (text: string): Todo[] => { + console.log(this.state); + const nextState = [ + ...this.state, + { + isCompleted: false, + title: text, + id: new Date().getTime().toString(), + }, + ]; + this.state = nextState; + return nextState; + }; } } diff --git a/src/components/Header.ts b/src/components/Header.ts index 349362a..ca073cf 100644 --- a/src/components/Header.ts +++ b/src/components/Header.ts @@ -1,9 +1,9 @@ import { HeaderProps } from '../util/types.js'; export default class Header { - $app: HTMLDivElement; - title: string; - $header: HTMLHeadElement; + private readonly $app: HTMLDivElement; + private readonly title: string; + private readonly $header: HTMLHeadElement; constructor({ $app, title }: HeaderProps) { this.$header = document.createElement('h1'); diff --git a/src/components/TodoCounter.ts b/src/components/TodoCounter.ts index 706e36e..9f5b8ca 100644 --- a/src/components/TodoCounter.ts +++ b/src/components/TodoCounter.ts @@ -1,8 +1,8 @@ import { Todo, TodoCounterProps } from '../util/types.js'; export default class TodoCounter { - state: Todo[]; - $counter: HTMLDivElement; + private state: Todo[]; + private readonly $counter: HTMLDivElement; constructor({ $app, initialState }: TodoCounterProps) { this.$counter = document.createElement('div'); diff --git a/src/components/TodoList.js b/src/components/TodoList.js index a67a84d..67a52cf 100644 --- a/src/components/TodoList.js +++ b/src/components/TodoList.js @@ -1,30 +1,16 @@ import { setItem } from '../util/storage.js'; const STORAGE_KEY = 'todo'; export default class TodoList { - constructor({ $app, todoInitialState, updateTodoCounter }) { + constructor({ $app, initialState, updateTodoCounter, onRemoveTodo, onToggleTodo, }) { this.$todoList = document.createElement('div'); $app.appendChild(this.$todoList); + this.onRemoveTodo = onRemoveTodo; + this.onToggleTodo = onToggleTodo; this.updateTodoCounter = updateTodoCounter; - this.state = todoInitialState; + this.state = initialState; this.setEvent(); this.render(); } - completedTodo(id) { - const nextState = this.state.map((todo) => { - if (todo.id === id) { - return { - ...todo, - isCompleted: !todo.isCompleted, - }; - } - return todo; - }); - this.setState(nextState); - } - removeTodo(id) { - const nextState = this.state.filter((todo) => todo.id !== id); - this.setState(nextState); - } setState(nextState) { setItem(STORAGE_KEY, JSON.stringify(nextState)); this.updateTodoCounter(nextState); @@ -54,9 +40,9 @@ export default class TodoList { this.$todoList.addEventListener('click', (event) => { const target = event.target; const id = target.dataset.id; - if (id !== null) { - target.className === 'toggled-text' && this.completedTodo(id); - target.className === 'remove-button' && this.removeTodo(id); + if (id) { + target.className === 'toggled-text' && this.onToggleTodo(id); + target.className === 'remove-button' && this.onRemoveTodo(id); } }); } diff --git a/src/components/TodoList.ts b/src/components/TodoList.ts index 7617d05..3d316c5 100644 --- a/src/components/TodoList.ts +++ b/src/components/TodoList.ts @@ -4,38 +4,30 @@ import { TodoListProps, Todo } from '../util/types.js'; const STORAGE_KEY = 'todo'; export default class TodoList { - $todoList: HTMLDivElement; - state: Todo[]; - updateTodoCounter: (nextState: Todo[]) => void; - - constructor({ $app, todoInitialState, updateTodoCounter }: TodoListProps) { + private readonly $todoList: HTMLDivElement; + private state: Todo[]; + private readonly updateTodoCounter: (nextState: Todo[]) => void; + private readonly onRemoveTodo: (id: string) => void; + private readonly onToggleTodo: (id: string) => void; + constructor({ + $app, + initialState, + updateTodoCounter, + onRemoveTodo, + onToggleTodo, + }: TodoListProps) { this.$todoList = document.createElement('div'); $app.appendChild(this.$todoList); + this.onRemoveTodo = onRemoveTodo; + this.onToggleTodo = onToggleTodo; this.updateTodoCounter = updateTodoCounter; - this.state = todoInitialState; + + this.state = initialState; this.setEvent(); this.render(); } - completedTodo(id: string): void { - const nextState = this.state.map((todo) => { - if (todo.id === id) { - return { - ...todo, - isCompleted: !todo.isCompleted, - }; - } - return todo; - }); - this.setState(nextState); - } - - removeTodo(id: string): void { - const nextState = this.state.filter((todo) => todo.id !== id); - this.setState(nextState); - } - setState(nextState: Todo[]): void { setItem(STORAGE_KEY, JSON.stringify(nextState)); this.updateTodoCounter(nextState); @@ -66,10 +58,10 @@ export default class TodoList { setEvent(): void { this.$todoList.addEventListener('click', (event) => { const target = event.target as HTMLElement; - const id = target.dataset.id as string | null; - if (id !== null) { - target.className === 'toggled-text' && this.completedTodo(id); - target.className === 'remove-button' && this.removeTodo(id); + const id = target.dataset.id as string; + if (id) { + target.className === 'toggled-text' && this.onToggleTodo(id); + target.className === 'remove-button' && this.onRemoveTodo(id); } }); } diff --git a/src/components/createTodoInput.js b/src/components/createTodoInput.js index 56b0366..c18c9c4 100644 --- a/src/components/createTodoInput.js +++ b/src/components/createTodoInput.js @@ -15,9 +15,11 @@ export default class createTodo { this.$form.addEventListener('submit', (e) => { e.preventDefault(); const $input = this.$form.querySelector('input[name="todo"]'); - const text = $input.value; - this.onSubmit(text); - $input.value = ''; + if ($input) { + const text = $input.value; + this.onSubmit(text); + $input.value = ''; + } }); } } diff --git a/src/components/createTodoInput.ts b/src/components/createTodoInput.ts index e628752..fb37b7f 100644 --- a/src/components/createTodoInput.ts +++ b/src/components/createTodoInput.ts @@ -1,9 +1,9 @@ import { createTodoProps } from '../util/types.js'; export default class createTodo { - $form: HTMLFormElement; - isInit: boolean; - onSubmit: (text: string) => void; + private readonly $form: HTMLFormElement; + private readonly isInit: boolean; + private readonly onSubmit: (text: string) => void; constructor({ $app, onSubmit }: createTodoProps) { this.$form = document.createElement('form'); @@ -22,12 +22,15 @@ export default class createTodo { if (!this.isInit) { this.$form.addEventListener('submit', (e) => { e.preventDefault(); - const $input = this.$form.querySelector( - 'input[name="todo"]', - ) as HTMLInputElement; - const text = $input.value; - this.onSubmit(text); - $input.value = ''; + + const $input = + this.$form.querySelector('input[name="todo"]'); + + if ($input) { + const text = $input.value; + this.onSubmit(text); + $input.value = ''; + } }); } } diff --git a/src/main.js b/src/main.js index a2f872e..281e29d 100644 --- a/src/main.js +++ b/src/main.js @@ -2,10 +2,8 @@ import App from './App.js'; import { getItem } from './util/storage.js'; const STORAGE_KEY = 'todo'; const $app = document.querySelector('#app'); -const todoInitialState = getItem(STORAGE_KEY, []); -// 여기서 as Todo[]를 안해도 에러가 발생하지 않지만 getItem의 반환값이 Todo[]라는 것을 -// 명시적으로 알려주기 위해 as Todo[]를 사용했습니다. +const initialState = getItem(STORAGE_KEY, []); new App({ $app, - initialState: todoInitialState, + initialState, }); diff --git a/src/main.ts b/src/main.ts index aed834e..70a0146 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,14 @@ import App from './App.js'; import { getItem } from './util/storage.js'; +import { Todo } from './util/types.js'; const STORAGE_KEY = 'todo'; const $app = document.querySelector('#app')!; -const todoInitialState = getItem(STORAGE_KEY, []); - -// 여기서 as Todo[]를 안해도 에러가 발생하지 않지만 getItem의 반환값이 Todo[]라는 것을 -// 명시적으로 알려주기 위해 as Todo[]를 사용했습니다. +const initialState = getItem(STORAGE_KEY, []); new App({ $app, - initialState: todoInitialState, + initialState, }); diff --git a/src/util/storage.js b/src/util/storage.js index cbd7e7b..5cc53d6 100644 --- a/src/util/storage.js +++ b/src/util/storage.js @@ -9,5 +9,11 @@ export const setItem = (key, value) => { }; export const getItem = (key, defaultValue) => { const storedValue = storage.getItem(key); - return storedValue ? JSON.parse(storedValue) : defaultValue; + try { + return storedValue ? JSON.parse(storedValue) : defaultValue; + } + catch (e) { + console.error('Error parsing stored value:', e); + return defaultValue; + } }; diff --git a/src/util/storage.ts b/src/util/storage.ts index 9b0631d..2406e3a 100644 --- a/src/util/storage.ts +++ b/src/util/storage.ts @@ -1,6 +1,8 @@ +import type { GetItem, SetItem } from './types'; + const storage = window.localStorage; -export const setItem = (key: string, value: string) => { +export const setItem: SetItem = (key, value) => { try { storage.setItem(key, value); } catch (e) { @@ -8,7 +10,12 @@ export const setItem = (key: string, value: string) => { } }; -export const getItem = (key: string, defaultValue: []) => { +export const getItem: GetItem = (key, defaultValue) => { const storedValue = storage.getItem(key); - return storedValue ? JSON.parse(storedValue) : defaultValue; + try { + return storedValue ? JSON.parse(storedValue) : defaultValue; + } catch (e) { + console.error('Error parsing stored value:', e); + return defaultValue; + } }; diff --git a/src/util/types.ts b/src/util/types.ts index 3641f5f..15a27aa 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -4,28 +4,31 @@ export interface Todo { title: string; } -export interface AppProps { +export interface TodoBasicProps { $app: HTMLDivElement; +} + +export interface InitialStateProps { initialState: Todo[]; } -export interface HeaderProps { - $app: HTMLDivElement; +export interface AppProps extends TodoBasicProps, InitialStateProps {} + +export interface TodoCounterProps extends TodoBasicProps, InitialStateProps {} + +export interface HeaderProps extends TodoBasicProps { title: string; } -export interface createTodoProps { - $app: HTMLDivElement; +export interface createTodoProps extends TodoBasicProps { onSubmit: (text: string) => void; } -export interface TodoListProps { - $app: HTMLElement; - todoInitialState: Todo[]; +export interface TodoListProps extends TodoBasicProps, InitialStateProps { updateTodoCounter: (nextState: Todo[]) => void; + onRemoveTodo: (id: string) => void; + onToggleTodo: (id: string) => void; } -export interface TodoCounterProps { - $app: HTMLDivElement; - initialState: Todo[]; -} +export type GetItem = (key: 'todo', defaultValue: T[]) => T[]; +export type SetItem = (key: 'todo', value: string) => void;