이 글의 목적
자꾸 완독이 안되다보니 첫 장만 반복해서 보게 되고 있었다.
점진적으로라도 나아가기위해 이해했던 부분만 알아볼 수 있게 적어 놓는다.
코드: https://github.com/Apress/frameworkless-front-end-development/tree/master/Chapter02/01
const element = targetElement.cloneNode(true)
element
하위 DOM에서 변경이 필요한 요소를 찾는다.const list = element.querySelector('.todo-list')
const counter = element.querySelector('.todo-count')
const filters = element.querySelector('.filters')
ex) todos
)와 함수(ex) getTodoElement
)로 통해 수정한다.list.innerHTML = todos.map(getTodoElement).join('')
counter.textContent = getTodoCount(todos)
requestAnimationFrame
를 통해 적절한 시점에 새로만든 요소(element)로 기존 요소를 대체한다.()window.requestAnimationFrame(() => {
const newMain = view(main, state)
main.replaceWith(newMain)
})
코드: https://github.com/Apress/frameworkless-front-end-development/tree/master/Chapter02/02
before:
const element = targetElement.cloneNode(true)
const list = element.querySelector('.todo-list')
// ..
list.innerHTML = todos.map(getTodoElement).join('')
counter.textContent = getTodoCount(todos)
Array
.from(filters.querySelectorAll('li a'))
.forEach(a => {
if (a.textContent === currentFilter) {
a.classList.add('selected')
} else {
a.classList.remove('selected')
}
})
after
const element = targetElement.cloneNode(true)
const list = element
.querySelector('.todo-list')
// ..
list.replaceWith(todosView(list, state))
counter.replaceWith(counterView(counter, state))
filters.replaceWith(filtersView(filters, state))
코드: https://github.com/Apress/frameworkless-front-end-development/tree/master/Chapter02/03
핵심
const registry = {
'dataset으로 매핑된 키값': view함수
}
html 요소에 변경이 필요한 요소마다 dataset 속성을 이용하여 마킹을 해준다.
해당 dataset의 값에 매칭되어야할 view함수들을 선언적으로 코딩한다.
registry.add('todos', todosView)
registry.add('counter', counterView)
registry.add('filters', filtersView)
부모 요소에서 dataset을 가진 요소를 전부 찾는다.
const childComponents = element.querySelectorAll('[data-component]')
해당 요소가 registry에 매핑된 view함수가 있다면 해당 함수를 호출한 결과값으로 교체한다.
const child = registry[name]
if (!child) {
return
}
target.replaceWith(child(target, state))
// before
window.requestAnimationFrame(() => {
const main = document.querySelector('.todoapp')
const newMain = appView(main, state)
main.replaceWith(newMain)
})
// after
registry.add('todos', todosView)
registry.add('counter', counterView)
registry.add('filters', filtersView)
window.requestAnimationFrame(() => {
const main = document.querySelector('.todoapp')
const newMain = registry.renderRoot(main, state)
main.replaceWith(newMain)
})
// before
const list = element.querySelector('.todo-list')
const counter = element.querySelector('.todo-count')
const filters = element.querySelector('.filters')
list.replaceWith(todosView(list, state))
counter.replaceWith(counterView(counter, state))
filters.replaceWith(filtersView(filters, state))
// after
const renderWrapper = component => {
return (targetElement, state) => {
const element = component(targetElement, state)
const childComponents = element
.querySelectorAll('[data-component]')
Array
.from(childComponents)
.forEach(target => {
const name = target
.dataset
.component
const child = registry[name]
if (!child) {
return
}
target.replaceWith(child(target, state))
})
return element
}
}
코드: https://github.com/Apress/frameworkless-front-end-development/tree/master/Chapter02/04
기존 코드의 문제:
가상DOM의 핵심 원리는 큰 요소를 바꿔끼는 비용보다, 변경되어야 하는 최소한의 부분을 계산하고 적용하는게 더 빠르다는 원리이다.
전체 변경 비용 > 변경해야할 부분을 찾기 + 변경된 부분(요소)만 변경
이런 "변경해야할 부분을 찾기"는 reconciliation이라 불린다.
React Reconciliation: https://ko.legacy.reactjs.org/docs/reconciliation.html
저자도 간단한 형태의 reconciliation함수를 만들었다.
형태는 다음과 같다.
applyDiff(부모요소, 실제돔, 가상돔)
if (realNode && !virtualNode) {
realNode.remove()
return
}
if (!realNode && virtualNode) {
parentNode.appendChild(virtualNode)
return
}
if (isNodeChanged(virtualNode, realNode)) {
realNode.replaceWith(virtualNode)
return
}
매우 간단하고 직관적이다