总结
- 渲染器的作用:把虚拟dom转换成真实dom
- 渲染器的原理:递归虚拟dom并用
appendChild
、createTextNode
来创建真实dom - template模版会被编译器编译成渲染函数(
render函数
)
渲染器
什么叫虚拟dom?
使用js对象来描述dom就是虚拟dom,render函数返回值就是虚拟dom
编写渲染器函数
html代码
<div @click="() => alert('hello')">click me</div>
将html转成虚拟dom
虚拟dom描述信息
- 标签是div
- 参数绑定点击事件
- 子节点是 click me 文本
const vnode = {
tag: "div",
props: {
onClick: () => alert("hello")
},
children: "click me"
}
渲染器函数
- renderer传参为虚拟dom、真实dom(挂载点)
- 首先根据虚拟dom的标签通过
document.createElement
创建真实dom - 遍历虚拟dom的属性,判断是否为on开头,如果是就是事件,使用
key.substr(2)
把key去掉on再使用toLowerCase()
把事件转小写,最后使用真实dom.addEventListener
绑定vnode.props[key]
事件 - 判断子节点是类型,文本?组件?
4.1 要是文本得话使用document.createTextNode(vnode.children)
创建文本节点再使用真实dom.appendChild
添加
4.2 要是多个子节点那么子节点就是一个数组,需要递归子节点 - 最终
container.appendChild(el)
绑定真实dom
function renderer(vnode, container) {
// 使用 vnode.tag 作为标签名称创建 DOM 元素
const el = document.createElement(vnode.tag)
// 遍历 vnode.props 将属性、事件添加到 DOM 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
// 如果 key 以 on 开头,那么说明它是事件
el.addEventListener(
key.substr(2).toLowerCase(), // 事件名称 onClick ---> click
vnode.props[key] // 事件处理函数
)
}
}
// 处理 children
if (typeof vnode.children === 'string') {
// 如果 children 是字符串,说明是元素的文本子节点
el.appendChild(document.createTextNode(vnode.children))
} else if (Array.isArray(vnode.children)) {
// 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点
vnode.children.forEach(child => renderer(child, el))
}
// 将元素添加到挂载点下
container.appendChild(el)
}
将vnode,body节点传入renderer
renderer(vnode, document.body) // body 作为挂载点
页面显示click me
点击触发点击事件
组件本质
组件就是一组dom元素的封装,可以定义一个函数来代表组件,而函数的返回值就代表组件要渲染的内容
const MyComponent = function () {
return {
tag: 'div',
props: {
onClick: () => alert('hello')
},
children: 'click me'
}
}
虚拟dom怎么描述组件
可以用虚拟dom的tag属性
const vnode = {
tag: MyComponent
}
渲染器函数添加判断组件逻辑
现在虚拟dom的tag除了字符串(节点)
还有函数(组件)
,把renderer函数拆分mountElement函数
、mountComponent函数
来处理对应tag
mountElement函数
一开始renderer函数就是处理普通节点,直接复制
function mountElement(vnode, container) {
// 使用 vnode.tag 作为标签名称创建 DOM 元素
const el = document.createElement(vnode.tag)
// 遍历 vnode.props 将属性、事件添加到 DOM 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
// 如果 key 以 on 开头,那么说明它是事件
el.addEventListener(
key.substr(2).toLowerCase(), // 事件名称 onClick ---> click
vnode.props[key] // 事件处理函数
)
}
}
// 处理 children
if (typeof vnode.children === 'string') {
// 如果 children 是字符串,说明是元素的文本子节点
el.appendChild(document.createTextNode(vnode.children))
} else if (Array.isArray(vnode.children)) {
// 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点
vnode.children.forEach(child => renderer(child, el))
}
// 将元素添加到挂载点下
container.appendChild(el)
}
mountComponent函数
因为组件是函数,需要执行vnode.tag()
得到组件的虚拟dom,再传入renderer
function mountComponent(vnode, container) {
// 调用组件函数,获取组件要渲染的内容(虚拟 DOM)
const subtree = vnode.tag()
// 递归调用 renderer 渲染 subtree
renderer(subtree, container)
}
renderer函数
function renderer(vnode, container) {
if (typeof vnode.tag === 'string') {
// 说明 vnode 描述的是标签元素
mountElement(vnode, container)
} else if (typeof vnode.tag === 'function') {
// 说明 vnode 描述的是组件
mountComponent(vnode, container)
}
}