각 컴포넌트는 자신이 담당하는 화면을 그리기 위해 가장 최소한의 책임과 의존성을 가져야 한다.
자바스크립트는 함수의 파라미터로 함수를 만들어서 넣을 수 있다.
- 이를 이용해 각 컴포넌트가 최소한의 의존성을 갖도록 만들 수 있다.

TodoInput의 역할에 대해 생각해보자!
W3C 에서 지정한 DOM에 이벤트 리스너를 등록하는 방법입니다.
EventTarget의 addEventListener() 메서드는 지정한 이벤트가 대상에 전달될 때마다 호출할 함수를 설정합니다.
일반적인 대상은 Element, Document, Window지만 XMLHttpRequest와 같이 이벤트를 지원하는 모든 객체를 대상으로 지정할 수 있습니다.
HTML
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
JS
// Function to change the content of t2
function modifyText() {
var t2 = document.getElementById("t2");
if (t2.firstChild.nodeValue == "three") {
t2.firstChild.nodeValue = "two";
} else {
t2.firstChild.nodeValue = "three";
}
}
// add event listener to table
var el = document.getElementById("outside");
el.addEventListener("click", modifyText, false);
//html
<input type="text" id="todo-input />
<input type="text" id="todo-input" />
<div id="todo-list"></div>
//js
document.querySelector('#todo-input').addEventListener('keydown', (e)=>{
if(e.keyCode === 13){
data.push({
text: e.target.value,
isCompleted: false
})
todoList.setState(data)
}
})
<main> 태그<main>태그는 해당 문서의 <body> 요소의 주 콘텐츠를 정의할 때 사용합니다.
<main> 요소의 콘텐츠는 해당 문서의 중심 주제 또는 주요 기능과 직접적으로 관련되어 있거나 확장되는 콘텐츠로 구성되어야 하며, 문서 전반에 걸쳐 반복되는 내용을 포함해서는 안 됩니다.
따라서 하나의 문서에는 단 하나의 <main> 요소만이 존재해야 하며, <main> 요소는 <article>, <aside>, <footer>, <header>, <nav> 요소의 자손 요소가 되어서는 안 됩니다.
한 노드를 특정 부모 노드의 자식 노드 리스트 중 마지막 자식으로 붙입니다.
var p = document.createElement("p");
document.body.appendChild(p);
function TodoInput($app) {
const $todoInput = document.createElement('input')
$app.appendChild($todoInput)
$todoInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
this.state = [
...this.state,
{
text: e.target.value,
isCompleted: false,
},
]
>>>>> this.todoList.setState(this.state)
e.target.value = ''
}
})
this.render = () => {}
}
파라미터로 함수를 넘긴다😉
App.js
function App($app, initialState) {
this.state = initialState
this.todoInput = new TodoInput($app, (text) => {
this.state = [
...this.state,
{
text,
isCompleted: false,
},
]
this.todoList.setState(this.state)
})
this.todoList = new TodoList($app, this.state)
}
TodoInput.js
function TodoInput($app, onAddTodo) {
const $todoInput = document.createElement('input')
$app.appendChild($todoInput)
$todoInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
onAddTodo(e.target.value)
e.target.value = ''
}
})
this.render = () => {}
}
✨컴포넌트들의 의존성을 최소화시키자!
const arr = [1,2,3]
const arr2 = arr
//arr 배열의 참조값을 갖게되서 arr과 같은 배열을 보고있게됨
arr.push(4)
//arr2에도 4가 추가됨
const arr3 = [...arr]
//arr의 값을 복사, arr과 다른 배열이 만들어짐
//arr에 push를 해도 추가되지 않음
this.render = function () {
this.$target.innerHTML = this.todos.map(({ text, isCompleted }, index) => `<li data-index="${index}">${isCompleted ? `<s>${text}</s>` : text}</li>`).join('')
// simple way
const $lis = this.$target.querySelectorAll('li')
$lis.forEach(($li) => {
$li.addEventListener('click', (e) => {
// 클릭한 li의 위치를 찾아라!
const index = parseInt(e.target.closest('li').dataset.index, 10)
this.todos = [...this.todos]
this.todos[index] = {
text: this.todos[index].text,
isCompleted: !this.todos[index].isCompleted,
}
this.setState(this.todos)
})
})
}
App.js에서 데이터를 관리하도록 했는데 TodoList의 data가 바뀌므로 이렇게 하면 안된다!
App에서 관리를 하려면 파라미터로 함수를 보내주는 방식으로 한다!
App.js
this.todoList = new TodoList({
$app,
todos: this.state,
onClick: (index) => {
const nextState = [...this.state]
nextState[index] = {
text: nextState[index].text,
isCompleted: !nextState[index].isCompleted,
}
this.state = nextState
this.todoList.setState(nextState)
},
})
TodoList.js
function TodoList({ $app, todos, onClick }) {
...
this.render = function () {
this.$target.innerHTML = this.todos.map(({ text, isCompleted }, index) => `<li data-index="${index}">${isCompleted ? `<s>${text}</s>` : text}</li>`).join('')
const $lis = this.$target.querySelectorAll('li')
$lis.forEach(($li) => {
$li.addEventListener('click', (e) => {
// 클릭한 li의 위치를 찾아라!
const index = parseInt(e.target.closest('li').dataset.index, 10)
onClick(index)
})
})
}

App 컴포넌트가 중앙 조율을 하는 구조!
App 컴포넌트에 setState추가
APP.js
function App($app, initialState) {
this.state = initialState
this.todoInput = new TodoInput({
$app,
onAddTodo: (text) => {
const nextState = [
...this.state,
{
text,
isCompleted: false,
},
]
this.setState(nextState)
},
})
this.todoList = new TodoList({
$app,
todos: this.state,
onClick: (index) => {
const nextState = [...this.state]
nextState[index] = {
text: nextState[index].text,
isCompleted: !nextState[index].isCompleted,
}
this.state = nextState
this.setState(nextState)
},
})
this.todoCount = new TodoCount({
$app,
todos: this.state,
})
this.setState = (nextState) => {
this.state = nextState
this.todoList.setState(this.state)
this.todoCount.setState(this.state)
}
}
TodoCount
function TodoCount({ $app, todos }) {
this.todos = todos
const $target = document.createElement('div')
$target.className = 'TodoCount'
$app.appendChild($target)
this.$target = $target
this.render = () => {
const totalCount = this.todos.length
const completedCount = this.todos.filter((todo) => todo.isCompleted).length
this.$target.innerHTML = `해야 할 일 총 갯수: ${totalCount} 완료된 할 일 갯수: ${completedCount}`
}
this.setState = (nextState) => {
this.todos = nextState
this.render()
}
this.render()
}
모듈과 모듈간의결합(스크립트 파일과 스크립트 파일간의 결합)을
html 파일에 적용하려면??
<script type="module" src="./index.js"></script>
script 태그에 type 속성 값을 module로 줘야한다!
그렇지 않으면 error남

: 하위에서 상위 요소로의 이벤트 전파 방식
: 이벤트 버블링과 반대 방향으로 진행되는 이벤트 전파 방식
: 위 API는 해당 이벤트가 전파되는 것을 막는다. 따라서 이벤트 버블링의 경우에는 클릭한 요소 이벤트만 발생시키고 상위요소로 이벤트를 전달하는 것을 방해한다. 그리고 이벤트 캡쳐의 경우 클릭한 요소의 최상위 요소의 이벤트만 동작시키고 하위 요소들로 이벤트를 전달하지 않는다.
: 하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식
리스트 아이템이 많아질수록 이벤트 리스너를 다는 작업은 매우 번거롭다. 이 문제를 해결할 수 있는 방법이 이벤트 위임이다.
다수의 자식요소에 각각 이벤트 핸들러를 바인딩하는 대신 하나의 부모 요소에 이벤트 핸들러를 바인딩하는 방법이다.
또한 DOM 트리에 새로운 li요소를 추가하더라도 이벤트 처리는 부모 요소인 ul 요소에 위임되었기 때문에 새로운 요소에 이벤트 핸들러를 다시 바인딩할 필요가 없다.