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/README.md b/README.md index e7140d8..71dffce 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ ## ⏰ 스터디 시간 -**매주 월요일 10:00** +~~매주 월요일 10:00~~ + +**월요일 16:00**
@@ -31,9 +33,8 @@ ## ⚙️ 컨벤션 * 본인 이름의 브랜치에 정리 파일 및 실습 파일 올리고 PR 보내기 -* 파일명 규칙 - * 본인 이름 폴더 / 챕터 {번호 및 제목} / {정리}.md - * 본인 이름 폴더 / 챕터 {번호 및 제목} / Practice / {문제}.ts +* 파일업로드 + * `본인 이름 폴더` / 내에 정리 md 파일, 예제문제 풀이 ts파일
@@ -43,5 +44,9 @@ | 회차 | 일시 | 스터디 내용 | | ---- | -------- | -------- | | 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 전환 | 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..f4c008a --- /dev/null +++ b/src/App.js @@ -0,0 +1,68 @@ +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 = makeNextState(text); + this.state = nextState; + todoList.setState(nextState); + todoCounter.setState(nextState); + }, + }); + const todoList = new TodoList({ + $app: this.$app, + 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, + }); + 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 new file mode 100644 index 0000000..827ebce --- /dev/null +++ b/src/App.ts @@ -0,0 +1,82 @@ +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 { + private readonly $app: HTMLDivElement; + private 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 = makeNextState(text); + this.state = nextState; + todoList.setState(nextState); + todoCounter.setState(nextState); + }, + }); + + const todoList = new TodoList({ + $app: this.$app, + 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, + }); + + 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.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..ca073cf --- /dev/null +++ b/src/components/Header.ts @@ -0,0 +1,19 @@ +import { HeaderProps } from '../util/types.js'; + +export default class Header { + private readonly $app: HTMLDivElement; + private readonly title: string; + private readonly $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..9f5b8ca --- /dev/null +++ b/src/components/TodoCounter.ts @@ -0,0 +1,27 @@ +import { Todo, TodoCounterProps } from '../util/types.js'; + +export default class TodoCounter { + private state: Todo[]; + private readonly $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..67a52cf --- /dev/null +++ b/src/components/TodoList.js @@ -0,0 +1,49 @@ +import { setItem } from '../util/storage.js'; +const STORAGE_KEY = 'todo'; +export default class TodoList { + 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 = initialState; + this.setEvent(); + this.render(); + } + 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) { + 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 new file mode 100644 index 0000000..3d316c5 --- /dev/null +++ b/src/components/TodoList.ts @@ -0,0 +1,68 @@ +import { setItem } from '../util/storage.js'; +import { TodoListProps, Todo } from '../util/types.js'; + +const STORAGE_KEY = 'todo'; + +export default class TodoList { + 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 = initialState; + this.setEvent(); + this.render(); + } + + 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; + 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 new file mode 100644 index 0000000..c18c9c4 --- /dev/null +++ b/src/components/createTodoInput.js @@ -0,0 +1,26 @@ +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"]'); + if ($input) { + 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..fb37b7f --- /dev/null +++ b/src/components/createTodoInput.ts @@ -0,0 +1,37 @@ +import { createTodoProps } from '../util/types.js'; + +export default class createTodo { + private readonly $form: HTMLFormElement; + private readonly isInit: boolean; + private readonly 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"]'); + + if ($input) { + 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..281e29d --- /dev/null +++ b/src/main.js @@ -0,0 +1,9 @@ +import App from './App.js'; +import { getItem } from './util/storage.js'; +const STORAGE_KEY = 'todo'; +const $app = document.querySelector('#app'); +const initialState = getItem(STORAGE_KEY, []); +new App({ + $app, + initialState, +}); diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..70a0146 --- /dev/null +++ b/src/main.ts @@ -0,0 +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 initialState = getItem(STORAGE_KEY, []); + +new App({ + $app, + initialState, +}); diff --git a/src/util/storage.js b/src/util/storage.js new file mode 100644 index 0000000..5cc53d6 --- /dev/null +++ b/src/util/storage.js @@ -0,0 +1,19 @@ +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); + 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 new file mode 100644 index 0000000..2406e3a --- /dev/null +++ b/src/util/storage.ts @@ -0,0 +1,21 @@ +import type { GetItem, SetItem } from './types'; + +const storage = window.localStorage; + +export const setItem: SetItem = (key, value) => { + try { + storage.setItem(key, value); + } catch (e) { + console.log(e); + } +}; + +export const getItem: GetItem = (key, defaultValue) => { + const storedValue = storage.getItem(key); + try { + return storedValue ? JSON.parse(storedValue) : defaultValue; + } catch (e) { + console.error('Error parsing stored value:', e); + return 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..15a27aa --- /dev/null +++ b/src/util/types.ts @@ -0,0 +1,34 @@ +export interface Todo { + isCompleted: boolean; + id: string; + title: string; +} + +export interface TodoBasicProps { + $app: HTMLDivElement; +} + +export interface InitialStateProps { + initialState: Todo[]; +} + +export interface AppProps extends TodoBasicProps, InitialStateProps {} + +export interface TodoCounterProps extends TodoBasicProps, InitialStateProps {} + +export interface HeaderProps extends TodoBasicProps { + title: string; +} + +export interface createTodoProps extends TodoBasicProps { + onSubmit: (text: string) => void; +} + +export interface TodoListProps extends TodoBasicProps, InitialStateProps { + updateTodoCounter: (nextState: Todo[]) => void; + onRemoveTodo: (id: string) => void; + onToggleTodo: (id: string) => void; +} + +export type GetItem = (key: 'todo', defaultValue: T[]) => T[]; +export type SetItem = (key: 'todo', value: string) => void; 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" + } +}