-
Notifications
You must be signed in to change notification settings - Fork 0
[동현] Web Component #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: donghyun
Are you sure you want to change the base?
Changes from all commits
2428a78
07483a3
98370ab
27852e1
2c11be2
43a7abf
9dedf62
599c0c5
94ab28d
a669436
ea6812d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "semi": false, | ||
| "printWidth": 140 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import Component from "../core" | ||
|
|
||
| export default class TodoInput extends Component<HTMLDivElement> { | ||
| template(): string { | ||
| return ` | ||
| <form> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프리티어 파일 설정할 때 정렬 관련 속성을 넣으면 읽기 편할 것 같아요ㅠㅠ
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정하겠습니다! |
||
| <input type="text" /> | ||
| <button>Submit</button> | ||
| </form> | ||
| ` | ||
| } | ||
|
|
||
| setEvent(): void { | ||
| this.addEvent("submit", "form", (event) => { | ||
| event.preventDefault() | ||
| const inputElem = this.target.querySelector("input") as HTMLInputElement | ||
| this.props.addTodo(inputElem.value) | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import Component from "../core" | ||
| import { Todo } from "../types" | ||
|
|
||
| interface TodoListProps { | ||
| todos: Todo[] | ||
| deleteTodo: (todoId: number) => void | ||
| toggleActive: (todoId: number) => void | ||
| } | ||
|
|
||
| export default class TodoList extends Component<HTMLUListElement, TodoListProps> { | ||
| template(): string { | ||
| const { todos } = this.props! | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ! 뭔가요??
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 |
||
| return ` | ||
| ${todos | ||
| .map( | ||
| (todo) => ` | ||
| <li data-id=${todo.id}> | ||
| ${todo.title} | ||
| <button type="button" id="toggle-button" style="color: ${todo.isActive ? "#09F" : "#F09"}"> | ||
| ${todo.isActive ? "비활성화" : "활성화"} | ||
| </button> | ||
| <button type="button" id="delete-button">삭제</button> | ||
| </li> | ||
| ` | ||
| ) | ||
| .join("")} | ||
| ` | ||
| } | ||
|
|
||
| getTodoId(element: HTMLElement): number { | ||
| const todoId = Number((element.closest("[data-id]") as HTMLLIElement)!.dataset.id) | ||
| return todoId | ||
| } | ||
|
|
||
| setEvent(): void { | ||
| this.addEvent("click", "#toggle-button", (event) => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아티클엔 클래스로 지정해썼는데 아이디로 바꾼 이유가 있을까요?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 클래스는 어디서든 사용할 수 있도록 하는 목표가 있지만, 여기에선 굳이? 싶어서 아이디로 사용했습니다. 그래도 확장성을 생각한다면 클래스로 가는것이 더 맞겠네요 |
||
| const todoId = this.getTodoId(event.target as HTMLElement) | ||
| this.props!.toggleActive(todoId) | ||
| }) | ||
|
|
||
| this.addEvent("click", "#delete-button", (event) => { | ||
| const todoId = this.getTodoId(event.target as HTMLElement) | ||
| this.props!.deleteTodo(todoId) | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| type ComponentInternalRecord = Record<string, any> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 아래 interface에 바로 선언하지 않고 타입을 선언해서 사용한 이유가 뭔가요? 동현님의 인터페이스와 타입을 사용하는 기준이 궁금합니다
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인터페이스를 사용하지 않은 이유는 확장을 할 필요가 없기 때문이였어요. 만약 인터페이스를 사용한다면 아래처럼 작성하게 되는데요. interface ComponentInternalRecord extends Record<string,any>{}이미 Record<string, any>이기 때문에 확장을 하는게 의미가 없다고 생각했어요. 따라서 |
||
|
|
||
| interface ComponentOptions<TElement = HTMLElement, Props = any, State extends ComponentInternalRecord = ComponentInternalRecord> { | ||
| target: TElement | ||
| props?: Props | ||
| state?: State | ||
| } | ||
|
|
||
| export default abstract class Component< | ||
| TElement extends HTMLElement, | ||
| Props = any, | ||
| State extends ComponentInternalRecord = ComponentInternalRecord | ||
| > { | ||
| target: TElement | ||
| props?: Props | ||
| private _state?: State | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불변성을 신경쓴 부분인거죠?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불변성이라기 보다, 외부에서 값을 접근할 수 없도록 하기 위함입니다! |
||
|
|
||
| constructor({ target, props, state = {} as State }: ComponentOptions<TElement, Props, State>) { | ||
| this.target = target | ||
| this.props = props | ||
| this._state = state | ||
|
|
||
| this.setup() | ||
| this.render() | ||
| this.setEvent() | ||
| } | ||
|
|
||
| setup() {} | ||
|
|
||
| get state() { | ||
| return { ...this._state! } | ||
| } | ||
|
|
||
| set state(newState: State) { | ||
| if (this._state) { | ||
| this._state = { ...this._state, ...newState } | ||
| this.render() | ||
| } | ||
| } | ||
|
|
||
| setState(newState: State) { | ||
| if (this._state) { | ||
| this._state = { ...this._state, ...newState } | ||
| this.render() | ||
| } | ||
| } | ||
|
|
||
| abstract template(): string | ||
|
|
||
| render() { | ||
| this.target.innerHTML = this.template() | ||
| this.componentDidMount() | ||
| } | ||
|
|
||
| componentDidMount() {} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 메서드 첨 알았어요
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 의미에 맞는진 아직 잘 모르겠네요,, 더 공부를 해봐야 정확하게 판단이 내려질것 같습니다 |
||
|
|
||
| addEvent(eventType: keyof HTMLElementEventMap, selector: keyof HTMLElementTagNameMap | string, callback: EventListener) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 굿 |
||
| this.target.addEventListener(eventType, (event) => { | ||
| const target = event.target as HTMLElement | ||
| if (target.closest(selector)) { | ||
| callback(event) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| setEvent() {} | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,72 @@ | ||
| // your Code | ||
| import Component from "./core" | ||
|
|
||
| import TodoInput from "./components/todo-Input" | ||
| import TodoList from "./components/todo-list" | ||
| import { Todo } from "./types" | ||
|
|
||
| interface TodoState { | ||
| todos: Todo[] | ||
| } | ||
|
|
||
| class App extends Component<HTMLDivElement, {}, TodoState> { | ||
| setup() { | ||
| this.state = { | ||
| todos: [], | ||
| } | ||
| } | ||
|
|
||
| componentDidMount(): void { | ||
| const todoInputElem = this.target.querySelector("#todo-input") as HTMLDivElement | ||
| const todoListElem = this.target.querySelector("#todo-list") as HTMLUListElement | ||
|
|
||
| new TodoInput({ | ||
| target: todoInputElem, | ||
| props: { | ||
| addTodo: this.addTodo.bind(this), | ||
| }, | ||
| }) | ||
|
|
||
| new TodoList({ | ||
| target: todoListElem, | ||
| props: { | ||
| todos: this.state!.todos, | ||
| deleteTodo: this.deleteTodo.bind(this), | ||
| toggleActive: this.toggleTodoActive.bind(this), | ||
| }, | ||
| }) | ||
| } | ||
|
|
||
| template(): string { | ||
| return ` | ||
| <main> | ||
| <div id="todo-input"></div> | ||
| <ul id="todo-list"></ul> | ||
| </main> | ||
| ` | ||
| } | ||
|
|
||
| addTodo(title: string) { | ||
| const newTodo = { | ||
| id: Date.now(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 신박합니다
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사실 이거는 굉장히 안좋습니다 ㅋㅋㅋㅋㅋㅋ 왜냐하면 id는 유일해야하는데 겹칠 수 있어요. 이런 것도 더 고민을 해봐야겠네요 |
||
| title, | ||
| isActive: false, | ||
| } as Todo | ||
| this.setState({ ...this.state, todos: [...this.state!.todos, { ...newTodo }] }) | ||
| } | ||
|
|
||
| deleteTodo(todoId: number) { | ||
| const filteredTodo = this.state!.todos.filter((todo) => todo.id !== todoId) | ||
| this.setState({ todos: filteredTodo }) | ||
| } | ||
|
|
||
| toggleTodoActive(todoId: number) { | ||
| const updateTodo = this.state!.todos.map((todo) => (todo.id === todoId ? { ...todo, isActive: !todo.isActive } : todo)) | ||
| this.setState({ todos: updateTodo }) | ||
| } | ||
| } | ||
|
|
||
| const rootElement = document.getElementById("app") as HTMLDivElement | ||
|
|
||
| if (rootElement) { | ||
| new App({ target: rootElement }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface Todo { | ||
| id: number | ||
| title: string | ||
| isActive: boolean | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도 타입을 지정했군요?? 근데 string 타입인가요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template은 템플릿 리터럴을 사용하는 것이기 때문에 반환 값을 string 타입으로 한거였어요. 사실 일부러 작성한 것 보다는 기존 Component에 타입이 정의되어 있어서 자동완성할 때 저렇게 되네요!