持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
3.2初识渲染器
现在我们大概能了解什么虚拟dom,它其实就是用对象来描述真实dom。这个对象的结构你可以自己设计,因为虚拟dom需要通过渲染器转化为真实dom,你只需要再实现一个对应你结构的渲染器即可,这也是本章的主要内容。、
之所以叫初识,因为渲染器如果专门来讲,可以讲上一天一夜,它还涉及到diff算法等,这一张只是大概的讲一下,后续会有专门章节。
这就是渲染器的基本功能,渲染器其实是很重要的功能,大部分时候,我们说起vue的虚拟dom,其实就包含虚拟dom结构设计和渲染器。
其实当你了解虚拟dom和渲染器之后,你可以自己写一些demo,实现一个简单的虚拟dom系统,假设我们现在有这样一个虚拟dom结构,我们约定,以on开头的都是事件,tag、children是渲染器用的,其他都是dom的属性
const vdom = {
tag:'button',
id:'button',
className:'div1',
children:'click Me!!!',
onClick(){
alert('hello world')
}
}
那么我们开始实现一下这个渲染器
const render = (vnode,container)=>{
//不存在tag就报错
if(!vnode.tag){
throw new Error('tag is required')
}
// 创建真实dom
const realDom = document.createElement(vnode.tag)
// 过滤一些内部用的keyName
const keys = Object.keys(vnode).filter(e=>!['tag','children'].includes(e))
//这里是dom的属性
const props = keys.filter(e=>!e.startsWith('on'))
//这里是事件
const events = keys.filter(e=>e.startsWith('on'))
//调用setAttribute设置dom的属性,这样更安全
props.forEach(item=>{
realDom.setAttribute(item,vnode[item])
})
//加入事件监听
events.forEach(item=>{
if(typeof vnode[item] === 'function'){
//转换时间名,onClick->click
const eventName = item.replace('on','').toLowerCase()
realDom.addEventListener(eventName,vnode[item])
}
})
if(vnode.children){
//如果children仅是字符串
if(typeof vnode.children === 'string'){
const textNode = document.createTextNode(vnode.children)
realDom.appendChild(textNode)
} else if(Array.isArray(vnode.children)) {
// 递归children
vnode.children.forEach(child=>render(child,realDom))
} else{
throw new Error('invalid children')
}
}
//判断一下container是字符串还是dom,然后插入dom
if(typeof container === 'string'){
document.querySelector(container)?.appendChild(realDom)
} else {
container.appendChild(realDom)
}
}
调用一下这个函数,render(vdom,'body')就会产生这个
点击按钮,发现click事件也加上
这就是一个非常非常简单的渲染器。总提来说,实现这个渲染器主要分为4步
- 以vdom.tag创建真实dom 2.处理属性与事件:在这个例子里,我把虚拟dom的格式扁平化了,通过on来判断是否是事件,然后分别处理。其实你也可以把事件写到on里,把属性写到props里.这样就好基本上是vue2的虚拟dom类型,这样更容易写判断。
const vdom = {
tag:'button',
on:{
click(){}
},
props:{}
}
3.处理children:这一步其实很关键,因为很多时候,我们的children可能有字符串,数组套vdom,甚至会有多层children,这里都需要处理,还有很多边界条件,我这里只是简单的写了一下。 4.插入真实dom
但是这样是不够的,这样的渲染器只能渲染固定的内容,每次都会插入,我们知道在实际使用中,不但有创建,更重要的是更新。
const vdom = {
tag:'button',
children:'click Me!!!',
}
render(vdom,'body')
setTimeout(()=>{
vdom.children = 'click Again'
render(vdom,'body')
},1000)
如果render函数里不做处理,你会发现它有渲染了一个新的按钮,这明显是不对的。这一块就是diff的过程了,书里后续会专门讲解。