<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>6_手写_将虚拟 Dom 转化为真实 Dom(类似的递归题-必考)</title>
</head>
<body>
<script>
const VDOM = {
tag: 'DIV',
attrs: {
id: 'app'
},
children: ['dd', {
tag: 'SPAN',
children: [{
tag: 'A',
children: ['aa']
}]
},
{
tag: 'SPAN',
children: [{
tag: 'A',
children: ['bb']
},
{
tag: 'A',
children: ['cc']
}
]
}
]
};
// 渲染函数
function _render(vnode) {
// 若是数字类型转化为字符串,因为 document.createTextNode(vnode: string)
if (typeof vnode === 'number') {
vnode = String(vnode)
}
if (typeof vnode === 'string') {
// 返回文本节点对象
return document.createTextNode(vnode)
}
// 生成普通 DOM
const dom = document.createElement(vnode.tag)
if (vnode.attrs) {
Object.keys(vnode.attrs).forEach((key) => {
const value = vnode.attrs[key]
dom.setAttribute(key, value)
})
}
// !重点 子数组进行递归操作
vnode.children.forEach((child) => dom.appendChild(_render(child)))
return dom
};
document.body.appendChild(_render(VDOM));
/*
!:类似面试题
*/
const el = {
tagName: 'ul',
children: [
'dd',
{
tagName: 'li',
props: {
class: 'item'
},
children: ['aa']
},
{
tagName: 'li',
props: {
class: 'item'
},
children: ['bb']
},
{
tagName: 'li',
props: {
class: 'item'
},
children: ['cc']
}
]
}
function vDOM(ele) {
if (Array.isArray(ele)) {
const res = []
ele.forEach(item => {
res.push(vDOM(item))
})
// 注意:此出返回的是一个数组
return res
} else if (Object.prototype.toString.call(ele) === '[object Object]') {
const {
tagName,
children
} = ele
const tag = document.createElement(tagName)
const child = vDOM(children)
// 注意:需要将返回的数组进行解构,然后将数组中的 DOM 节点 append 到创建的 DOM 元素上。使用展开运算符...
tag.append(...child)
return tag
} else {
const dom = document.createTextNode(ele)
return dom
}
}
document.body.appendChild(vDOM(el));
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>渲染器</title>
</head>
<body>
<script>
const vnode = {
tag: 'div',
props: {
id: 'root',
onClick: () => alert('hello')
},
children: 'click me'
};
function renderer(vnode, container) {
const el = document.createElement(vnode.tag)
// 遍历 props 对象
for (const key in vnode.props) {
// 重点。。。。
if (/^on/.test(key)) {
el.addEventListener(key.substr(2).toLowerCase(), vnode.props[key])
}else {
el.setAttribute(key + '', vnode.props[key])
}
}
// 处理 children
if (typeof vnode.children === 'string') {
el.appendChild(document.createTextNode(vnode.children))
} else if (Array.isArray(vnode.children)) {
vnode.children.forEach(child => renderer(child, el))
}
// 将元素添加到挂载节点下
container.appendChild(el)
}
renderer(vnode, document.body);
</script>
</body>
</html>
涉及到的知识点
append()
appendChild()
document.createElement()
document.createTextNode()
dom.setAttribute(key, value)
Object.prototype.toString.call(ele) === '[object Object]'
append 和 appendChild 的区别
DOM 中有两个常用的方法:
- append
- appendChild
你知道它们的区别吗?先看 TypeScript 类型:
append(...nodes: (Node | string)[]): void;
appendChild<T extends Node>(node: T): T;
参数类型不同
append
可插入Node节点或字符串,但 appendChild 只能插入Node节点,例如:
- 插入节点对象 ```typescript const parent = document.createElement(‘div’) const child = document.createElement(‘p’)
parent.append(child) //
parent.appendChild(child) //
- 插入字符串
```typescript
const parent = document.createElement('div')
parent.append('text') // <div>text</div>
// Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'
parent.appendChild('text')
参数个数不同
append
可传入多个参数;但 appendChild
只接受一个参数。
const parent = document.createElement('div')
const child = document.createElement('p')
const childTwo = document.createElement('p')
// <div><p></p><p></p>Hello world</div>
parent.append(child, childTwo, 'Hello world')
// <div><p></p></div>
parent.appendChild(child, childTwo, 'Hello world')
返回值不同
append
没有返回值,但 appendChild
返回插入的节点对象:
const parent = document.createElement('div')
const child = document.createElement('p')
const appendValue = parent.append(child)
console.log(appendValue) // undefined
const appendChildValue = parent.appendChild(child)
console.log(appendChildValue) // <p><p>