渲染系统局部模拟实现
const h = (tag, props, children) => {
// vnode => JavaScript对象 -> {}
return {
tag,
props,
children
}
}
const mounted = (vnode, container) => {
// 1.创建出真实的原生,并且在vnode上保留el
const el = vnode.el = document.createElement(vnode.tag)
// console.log('render', el, vnode.el)
// 2.处理props
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key]
if (key.startsWith('on')) {
el.addEventListener(key.slice(2).toLowerCase(), value)
} else {
el.setAttribute(key, value)
}
}
}
// 3.处理children
if (vnode.children) {
if (typeof vnode.children == 'string') {
el.textContent = vnode.children
} else {
vnode.children.forEach(item => {
mounted(item, el)
});
}
}
// 4.将el挂载到container上
container.appendChild(el)
}
const patch = (n1, n2) => {
if (n1.tag !== n2.tag) {
// 拿到当前元素 n1.el
const n1ElParent = n1.el.parentElement
n1ElParent.removeChild(n1.el)
mount(n2, n1ElParent)
} else {
// 1.取出element对象,并且在n2中进行保存
const el = n2.el = n1.el
// console.log('patch-el', el)
// 2.处理props
const oldProps = n1.props || {}
const newProps = n2.props || {}
// 2.1 获取所有的newProps添加到el
for (const key in newProps) {
const oldValue = oldProps[key]
const newValue = newProps[key]
if (newValue !== oldValue) {
if (key.startsWith('on')) {
el.addEventListener(key.slice(2).toLowerCase(), newValue)
} else {
el.setAttribute(key, newValue)
}
}
}
// 2.2 删除旧的props(旧的属性在新的属性没有就删除)
for (const key in oldProps) {
if (key.startsWith('on')) {
const value = oldProps[key]
el.removeEventListener(key.slice(2).toLowerCase(), value)
}
if (!key in newProps) {
el.removeAttribute(key)
}
}
// 3.处理children
const oldChildren = n1.children || []
const newChildren = n2.children || []
// 情况1:newChildren本身是一个string类型
if (typeof newChildren === 'string') {
// el.innerHTML = newChildren // 还可以做更细的判断:前后的字符串是都一样,一样则无需更改
// 边界情况
if(typeof oldChildren === 'string') {
if (newChildren !== oldChildren) {
// console.log('el', el)
el.textContent = newChildren
}
} else {
el.innerHTML = newChildren
}
} else {
// 情况2:newChildren本身就是一个数组
if(typeof oldChildren === 'string') {
el.innerHTML = ''
newChildren.forEach(item => {
mount(item, el)
})
} else {
/***
* 例如
* oldChildren [v1, v2, v3]
* newChildren [v1, v5, v6, v8, v9]
*/
/**
* 拿到两个数组中更短的
* 如果newLength < oldLength 则把旧的多出来的删除
* 如果newLength > oldLength 则把新的多出来的直接增加
*/
const commonLength = Math.min(oldChildren.length, newChildren.length)
// 1.前面有 相同节点 的原生进行patch操作
for (let i = 0; i < commonLength; i++) {
patch(oldChildren[i], newChildren[i])
}
// 2.newLength > oldLength
if (newChildren.length > oldChildren.length) {
newChildren.slice(oldChildren.length).forEach(item => {
// 挂载
mount(item, el)
})
}
// 2.newLength < oldLength
if (newChildren.length < oldChildren.length) {
oldChildren.slice(newChildren.length).forEach(item => {
// 移除多余的内容(卸载unmount)
el.removeChild(item.el)
})
}
}
}
}
}