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 = `
+
+ ${this.state
+ .map(({ title, isCompleted, id }) => {
+ return `
+ -
+ ${title}
+
+
+ `;
+ })
+ .join('')}
+
+ `;
+ }
+ 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 = `
+
+ ${this.state
+ .map(({ title, isCompleted, id }: Todo) => {
+ return `
+ -
+ ${title}
+
+
+ `;
+ })
+ .join('')}
+
+ `;
+ }
+
+ 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;