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 = `
+
+ ${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) {
+ 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 = `
+
+ ${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;
+ 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"
+ }
+}