-
Notifications
You must be signed in to change notification settings - Fork 4
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
[Step 4 실습] 옵저버 패턴 학습 (전진우) #29
base: Jun-Jinu
Are you sure you want to change the base?
Changes from all commits
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,12 @@ | ||
<!DOCTYPE html> | ||
<html lang="ko"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<link rel="stylesheet" href="style.css"> | ||
<title>4주차 JavaScript 실습</title> | ||
</head> | ||
<body> | ||
<div id="app"></div> | ||
<script src="./src/App.js" type="module"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import Component from "./Component.js"; | ||
|
||
import ItemAppender from "./components/ItemAppender.js"; | ||
import ItemsView from "./components/ItemsView.js"; | ||
|
||
import { observable } from "./Observer.js"; | ||
|
||
class App extends Component { | ||
init() { | ||
this.state = observable({ | ||
todoItems: [ | ||
{ name: "코딩하기", done: false, updateState: false }, | ||
{ name: "밥먹기", done: true, updateState: false }, | ||
{ name: "양치하기", done: false, updateState: false }, | ||
], | ||
}); | ||
} | ||
|
||
template() { | ||
return ` | ||
<h1>4주차 미션 - 옵저버 </h1> | ||
|
||
<div id="item-appender"></div> | ||
<div id="items-view"></div> | ||
`; | ||
} | ||
|
||
mount() { | ||
//투두리스트 state를 불러옴 | ||
const { todoItems } = this.state; | ||
const $itemAppender = this.$component.querySelector("#item-appender"); | ||
const $itemsView = this.$component.querySelector("#items-view"); | ||
|
||
// state를 props로 전달 | ||
new ItemAppender($itemAppender); | ||
new ItemsView($itemsView, { todoItems }); | ||
} | ||
|
||
setEvents() { | ||
this.appendTodoItem(); | ||
this.deleteTodoItem(); | ||
this.toggleTodoItem(); | ||
this.updateTodoItem(); | ||
} | ||
|
||
appendTodoItem() { | ||
const { todoItems } = this.state; | ||
const appendBtn = this.$component.querySelector("#append-btn"); | ||
|
||
if (appendBtn) { | ||
appendBtn.addEventListener("click", () => { | ||
const newTodo = | ||
this.$component.querySelector("#append-input").value; | ||
|
||
this.setState({ | ||
todoItems: [ | ||
...todoItems, | ||
observable({ | ||
name: newTodo, | ||
done: false, | ||
updateState: false, | ||
}), | ||
], | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
deleteTodoItem() { | ||
const { todoItems } = this.state; | ||
const $itemsView = this.$component.querySelector("#items-view"); | ||
|
||
$itemsView.addEventListener("click", (event) => { | ||
if (event.target.id === "delete-btn") { | ||
const todoIndex = parseInt( | ||
event.target.closest("li").getAttribute("data-id") | ||
); | ||
console.log(todoIndex); | ||
|
||
const deletedTodoItems = todoItems.filter( | ||
(item, index) => index !== todoIndex | ||
); | ||
this.setState({ todoItems: deletedTodoItems }); | ||
} | ||
}); | ||
} | ||
|
||
toggleTodoItem() { | ||
const { todoItems } = this.state; | ||
const $itemsView = this.$component.querySelector("#items-view"); | ||
|
||
$itemsView.addEventListener("change", (event) => { | ||
if (event.target.id === "toggle-btn") { | ||
const todoIndex = parseInt( | ||
event.target.closest("li").getAttribute("data-id") | ||
); | ||
const toggledTodoItems = [...todoItems]; | ||
toggledTodoItems[todoIndex] = { | ||
...toggledTodoItems[todoIndex], | ||
done: !toggledTodoItems[todoIndex].done, | ||
}; | ||
this.setState({ todoItems: toggledTodoItems }); | ||
} | ||
}); | ||
} | ||
|
||
updateTodoItem() { | ||
const { todoItems } = this.state; | ||
const $itemsView = this.$component.querySelector("#items-view"); | ||
|
||
$itemsView.addEventListener("click", (event) => { | ||
if (event.target.id === "update-btn") { | ||
const todoIndex = parseInt( | ||
event.target.closest("li").getAttribute("data-id") | ||
); | ||
|
||
const updatedTodoItems = [...todoItems]; | ||
updatedTodoItems[todoIndex].updateState = | ||
!updatedTodoItems[todoIndex].updateState; | ||
|
||
if (!updatedTodoItems[todoIndex].updateState) { | ||
const $itemsTitle = this.$component.querySelector( | ||
`#title-${todoIndex}` | ||
); | ||
|
||
updatedTodoItems[todoIndex].name = $itemsTitle.value; | ||
} | ||
|
||
this.setState({ todoItems: updatedTodoItems }); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
new App(document.querySelector("#app")); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { observable, observe } from "./Observer.js"; | ||
|
||
export default class Component { | ||
$component; | ||
$props; | ||
state; | ||
|
||
constructor($component, $props) { | ||
this.$component = $component; | ||
this.$props = $props; | ||
this.init(); | ||
this.updateState(); | ||
this.render(); | ||
} | ||
|
||
template() { | ||
return "<div></div>"; | ||
} | ||
|
||
mount() { | ||
/* 하위 컴포넌트 마운트 */ | ||
} | ||
|
||
render() { | ||
this.$component.innerHTML = this.template(); | ||
this.mount(); | ||
this.setEvents(); | ||
} | ||
|
||
setState(newState) { | ||
this.state = { ...this.state, ...newState }; | ||
this.render(); | ||
} | ||
|
||
init() { | ||
//state 초기화 | ||
// this.state = observable({ | ||
// todoItems: [ | ||
// { name: "코딩하기", done: false, updateState: false }, | ||
// { name: "밥먹기", done: true, updateState: false }, | ||
// { name: "양치하기", done: false, updateState: false }, | ||
// ], | ||
// }); | ||
} | ||
|
||
setEvents() {} | ||
|
||
updateState() { | ||
observe(() => { | ||
this.render(); | ||
console.log("렌더링"); | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,66 @@ | ||||||||||||||||||||||||||||||||||
function debounce(func) { | ||||||||||||||||||||||||||||||||||
let frameId; | ||||||||||||||||||||||||||||||||||
return function () { | ||||||||||||||||||||||||||||||||||
if (frameId) { | ||||||||||||||||||||||||||||||||||
cancelAnimationFrame(frameId); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
frameId = requestAnimationFrame(func); | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
Comment on lines
+1
to
+9
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.
Suggested change
바로 cancel 해버려도 되지 않을까요!? |
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
let currentObserver = null; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
export const observe = (func) => { | ||||||||||||||||||||||||||||||||||
currentObserver = debounce(func); | ||||||||||||||||||||||||||||||||||
func(); | ||||||||||||||||||||||||||||||||||
currentObserver = null; | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
export const observable = (obj) => { | ||||||||||||||||||||||||||||||||||
const observers = new Map(); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return new Proxy(obj, { | ||||||||||||||||||||||||||||||||||
get(target, key) { | ||||||||||||||||||||||||||||||||||
const value = target[key]; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (value !== null && typeof value === "object") | ||||||||||||||||||||||||||||||||||
return observable(value); | ||||||||||||||||||||||||||||||||||
Comment on lines
+26
to
+27
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. 오... 객체면 다시 한 번 씌워주는군요 ㅎㅎ 좋습니다 👏👏👏 |
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (currentObserver && typeof observers !== "undefined") { | ||||||||||||||||||||||||||||||||||
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. observers가 undefined일 수가 있나요? |
||||||||||||||||||||||||||||||||||
if (!observers.has(key)) observers.set(key, new Set()); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
observers.get(key).add(currentObserver); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return value; | ||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||
set(target, key, value) { | ||||||||||||||||||||||||||||||||||
target[key] = value; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (observers.has(key)) | ||||||||||||||||||||||||||||||||||
observers.get(key).forEach((observer) => observer()); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// const state = observable({ | ||||||||||||||||||||||||||||||||||
// todoItems: [ | ||||||||||||||||||||||||||||||||||
// { name: "코딩하기", done: false, updateState: false }, | ||||||||||||||||||||||||||||||||||
// { name: "밥먹기", done: true, updateState: false }, | ||||||||||||||||||||||||||||||||||
// { name: "양치하기", done: false, updateState: false }, | ||||||||||||||||||||||||||||||||||
// ], | ||||||||||||||||||||||||||||||||||
// }); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// observe(() => | ||||||||||||||||||||||||||||||||||
// console.log(state.todoItems[0].name + " 로그가 실행이 됐습니다.") | ||||||||||||||||||||||||||||||||||
// ); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// state.todoItems[0].name = "todo"; | ||||||||||||||||||||||||||||||||||
// state.todoItems[0].name = "todo1"; | ||||||||||||||||||||||||||||||||||
// state.todoItems[0].name = "todo2"; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// requestAnimationFrame(() => { | ||||||||||||||||||||||||||||||||||
// state.todoItems[0].name = "todo3"; | ||||||||||||||||||||||||||||||||||
// }); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import Component from "../Component.js"; | ||
|
||
export default class ItemAppender extends Component { | ||
template() { | ||
return ` | ||
<div class="input-container"> | ||
<input type="text" placeholder="새로운 할 일을 입력해주세요" id="append-input" class="append-input"/> | ||
<button class="btn" id="append-btn">추가</button> | ||
</div> | ||
`; | ||
} | ||
} | ||
Comment on lines
+3
to
+12
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. 앞선 내용과 동일합니다! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import Component from "../Component.js"; | ||
|
||
import { observe } from "../Observer.js"; | ||
|
||
export default class ItemsView extends Component { | ||
updateState() { | ||
observe(() => { | ||
console.log("ItemsView 컴포넌트에서 옵저버..."); | ||
console.log(this.$props); | ||
}); | ||
Comment on lines
+7
to
+10
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.
이런 흐름인데 지금은 observe에 observable로 씌워진 친구가 없어서, updateState는 최초에 1회만 실행되겠네요! |
||
} | ||
|
||
template() { | ||
const { todoItems } = this.$props; | ||
|
||
return ` | ||
<ul> | ||
${todoItems | ||
.map( | ||
({ done, name, updateState }, index) => ` | ||
<li data-id="${index}"> | ||
<input type="checkbox" ${ | ||
done ? "checked" : "" | ||
} id="toggle-btn" ${updateState ? "class='updated'" : ""}/> | ||
<input type="text" ${ | ||
done ? "class='todo checked'" : "class='todo'" | ||
} id="title-${index}" value="${name}" ${ | ||
updateState ? "" : "readOnly" | ||
} /> | ||
<button class="btn" id="update-btn">${ | ||
updateState ? "완료" : "수정" | ||
}</button> | ||
<button class="btn deleteBtn" id="delete-btn">삭제</button> | ||
</li>` | ||
) | ||
.join("")} | ||
</ul> | ||
|
||
`; | ||
} | ||
Comment on lines
+13
to
+40
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. 곰곰이 생각해보면, ItemsViews 컴포넌트는 클래스로 만들 필요 자체가 없는거죠..! |
||
} |
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.
updateState에서도 render를 실행하고 있기 때문에, 컴포넌트가 생성되는 시점에 render 함수가 총 두 번 실행될 것 같아요!