实现迷你Vue
我们把之前实现的内容合并到一起,尝试组合成一个迷你的Vue
完成以下内容:
html
<div id="app"></div>
<script>
function h(tag, props, children) {
return {
tag,
props,
children
}
}
function mount(vNode, container) {
const { tag, data, children } = vNode
const el = vNode.el = document.createElement(tag)
if(data) {
Object.keys(data).forEach(key => {
el.setAttribute(key, data[key])
})
}
if(typeof children === 'string') {
el.textContent = children
} else {
children.forEach(child => {
mount(child, el)
})
}
container.appendChild(el)
}
function patch(n1, n2) {
if(n1.tag === n2.tag) {
const el = n1.el
// props
const oldProps = n1.props
const newProps = n2.props
for(const key in newProps) {
if(oldProps[key] !== newProps[key]) {
el.setAttribute(key, newProps[key])
}
}
for(const key in oldProps) {
if(!newProps[key]) {
el.removeAttribute(key)
}
}
// children
const oldChildren = n1.children
const newChildren = n2.children
if(typeof newChildren === 'string') {
if(typeof oldChildren === 'string') {
if(newChildren !== oldChildren) {
el.textContent = newChildren
}
} else {
el.innerHTML = children
}
} else {
if(typeof oldChildren === 'string') {
mount(child, el)
} else {
const oldChildrenLen = n1.children.length
const newChildrenLen = n2.children.length
const commonLen = Math.min(oldChildrenLen, newChildrenLen)
for(let i = 0; i < newChildrenLen; i++) {
patch(oldChildren[i], newChildren[i])
}
if(newChildrenLen > oldChildrenLen) {
// 节点增加了
newChildren.slice(oldChildrenLen).forEach(child => {
mount(child, el)
})
} else if (newChildrenLen < oldChildrenLen) {
// 节点减少了
oldChildren.slice(newChildrenLen).forEach(child => {
el.removeChild(child.el)
})
}
}
}
} else {
// replace
}
}
mount(vNode1, document.getElementById('app'))
patch(vNode1, vNode2)
class Dep {
subscribers = new Set()
depend() {
if(activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify() {
this.subscribers.forEach(effect => {
effect()
})
}
}
const targetMap = new WeakMap()
const handler = {
get(target, key, receiver) {
const dep = getDep(target, key)
dep.depend()
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
const dep = getDep(target, key)
const result = Reflect.set(target, key, newValue, receiver)
dep.notify()
return result
}
}
function reactive(raw) {
return new Proxy(raw, handler)
}
function getDep(target, key) {
let map = targetMap.get(target)
if(!map) {
map = new Map()
targetMap.set(target, map)
}
let dep = map.get(key)
if(!dep) {
dep = new Dep()
map.set(key, dep)
}
return dep
}
let activeEffect = null
function watchEffect(effect) {
activeEffect = effect
effect()
activeEffect = null
}
// TODO
</script>
答案
html
<div id="app"></div>
<script>
function h(tag, props, children) {
return {
tag,
props,
children
}
}
function mount(vNode, container) {
const { tag, props, children } = vNode
const el = vNode.el = document.createElement(tag)
if(props) {
Object.keys(props).forEach(key => {
// 新增事件判断
if(key.startsWith('on')) {
el.addEventListener(key.slice(2).toLowerCase(), props[key])
} else {
el.setAttribute(key, props[key])
}
})
}
if(typeof children === 'string') {
el.textContent = children
} else {
children.forEach(child => {
mount(child, el)
})
}
container.appendChild(el)
}
function patch(n1, n2) {
if(n1.tag === n2.tag) {
const el = n2.el = n1.el
// props
const oldProps = n1.props
const newProps = n2.props
for(const key in newProps) {
if(oldProps[key] !== newProps[key]) {
el.setAttribute(key, newProps[key])
}
}
for(const key in oldProps) {
if(!newProps[key]) {
el.removeAttribute(key)
}
}
// children
const oldChildren = n1.children
const newChildren = n2.children
if(typeof newChildren === 'string') {
if(typeof oldChildren === 'string') {
if(newChildren !== oldChildren) {
el.textContent = newChildren
}
} else {
el.innerHTML = children
}
} else {
if(typeof oldChildren === 'string') {
mount(child, el)
} else {
const oldChildrenLen = n1.children.length
const newChildrenLen = n2.children.length
const commonLen = Math.min(oldChildrenLen, newChildrenLen)
for(let i = 0; i < newChildrenLen; i++) {
patch(oldChildren[i], newChildren[i])
}
if(newChildrenLen > oldChildrenLen) {
// 节点增加了
newChildren.slice(oldChildrenLen).forEach(child => {
mount(child, el)
})
} else if (newChildrenLen < oldChildrenLen) {
// 节点减少了
oldChildren.slice(newChildrenLen).forEach(child => {
el.removeChild(child.el)
})
}
}
}
} else {
// replace
}
}
class Dep {
subscribers = new Set()
depend() {
if(activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify() {
this.subscribers.forEach(effect => {
effect()
})
}
}
const targetMap = new WeakMap()
const handler = {
get(target, key, receiver) {
const dep = getDep(target, key)
dep.depend()
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
const dep = getDep(target, key)
const result = Reflect.set(target, key, newValue, receiver)
dep.notify()
return result
}
}
function reactive(raw) {
return new Proxy(raw, handler)
}
function getDep(target, key) {
let map = targetMap.get(target)
if(!map) {
map = new Map()
targetMap.set(target, map)
}
let dep = map.get(key)
if(!dep) {
dep = new Dep()
map.set(key, dep)
}
return dep
}
let activeEffect = null
function watchEffect(effect) {
activeEffect = effect
effect()
activeEffect = null
}
// TODO
const App = {
data: reactive({
count: 0
}),
render() {
return h('div', {
onClick: () => {
this.data.count++
}
}, String(this.data.count)) // 使用String是因为我们没有对数字类型进行处理
}
}
function mountApp(component, container) {
let isMounted = false
let oldVNode = null
watchEffect(() => {
if(!isMounted) {
oldVNode = component.render()
mount(oldVNode, container)
isMounted = true
} else {
let newVNode = component.render()
patch(oldVNode, newVNode)
}
})
}
mountApp(App, document.getElementById('app'))
</script>