初识虚拟DOM渲染器
- 什么是虚拟DOM
- 什么是渲染器
- 渲染器的实现
- 组件是什么
什么是虚拟DOM
首先简单说一下什么是虚拟DOM,虚拟DOM就是一个描述真实DOM的JS对象
例如:
真实的DOM元素
<div onClick="alert('click me')">click me</div>
可以用下面的JS对象(虚拟DOM)描述,再通过渲染器渲染成真实的DOM
// 描述虚拟DOM对象
const vnode = {
tag: "div",
props: {
onClick: () => {
alert("click, me")
}
},
children: "click me"
}
什么是渲染器
渲染器的作用就是把虚拟DOM渲染成真实的DOM,如下图所示:
渲染器的实现
详细看代码描述:
重点看 mountElement
函数部分,描述如何通过js对象渲染出真实的DOM元素
/*
* File Created: Monday, 6th March 2023 6:34:24 pm
* Author: hotsuitor (hotsuitor@qq.com)
* -----
* Last Modified: Monday, 6th March 2023 6:34:50 pm
* Modified By: hotsuitor (hotsuitor@qq.com>)
* -----
* Copyright 2022 - 2023 Your Company, Your Company
*/
/**
* 渲染器
* @param {*} vnode 虚拟DOM对象
* @param {*} container 真实的DOM元素,作为挂载点,
* 渲染器会把虚拟DOM渲染到该挂载点下
*/
function renderer(vnode, container) {
if (vnode.tag === 'string') {
// 说明描述的是标签元素
mountElement(vnode, container)
}
}
/**
* 渲染标签元素
* @param {*} vnode
* @param {*} container
*/
function mountElement(vnode, container) {
// 使用vnode.tag作为标签名创建DOM元素
const el = document.createElement(vnode.tag)
// 遍历 vnode.props ,将属性、事件添加到DOM元素
for (const key in vnode.props) {
// on开头表示是事件
if (/^on/.test(key)) {
// 添加事件,onClick->click, 事件注册
el.addEventListener(key.substring(2).toLowerCase(), vnode.props[key])
}
}
// 递归处理 children
if (typeof vnode.children === 'string') {
// 是字符串,说明是文本节点,直接添加到父元素
el.appendChild(document.createTextNode(vnode.children))
} else if (Array.isArray(vnode.children)) {
// 遍历处理children子节点
vnode.children.forEach((child) => renderer(child, el))
}
// 将元素添加到挂在点下
container.appendChild(el)
}
// demo
const vnode = {
tag: 'div',
props: {
onClick: () => {
alert('click me!')
},
},
children: 'click me',
}
renderer(vnode, document.body)
组件是什么
现在项目开发基本离不开组件的封装,我们再看一下组件是什么?
组件的本质是一组DOM元素的封装,组件是由一组虚拟DOM元素组成的内容。目的是使代码可复用性提高,不必写冗余代码
下面看代码实现,重点看 mountComponent
函数部分,组件是一个封装了一组虚拟DOM的对象。通过复用这个对象的虚拟DOM,就可以实现了组件的复用。
/*
* File Created: Monday, 6th March 2023 6:34:24 pm
* Author: hotsuitor (hotsuitor@qq.com)
* -----
* Last Modified: Monday, 6th March 2023 6:34:50 pm
* Modified By: hotsuitor (hotsuitor@qq.com>)
* -----
* Copyright 2022 - 2023 Your Company, Your Company
*/
/**
* 渲染器
* @param {*} vnode 虚拟DOM对象
* @param {*} container 真实的DOM元素,作为挂载点,
* 渲染器会把虚拟DOM渲染到该挂载点下
*/
function renderer(vnode, container) {
if (vnode.tag === 'string') {
// 说明描述的是标签元素
mountElement(vnode, container)
} else if (vnode.tag === 'object') {
// 描述的是组件
mountComponent(vnode, container)
}
}
/**
* 渲染标签元素
* @param {*} vnode
* @param {*} container
*/
function mountElement(vnode, container) {
// 使用vnode.tag作为标签名创建DOM元素
const el = document.createElement(vnode.tag)
// 遍历 vnode.props ,将属性、事件添加到DOM元素
for (const key in vnode.props) {
// on开头表示是事件
if (/^on/.test(key)) {
// 添加事件,onClick->click, 事件注册
el.addEventListener(key.substring(2).toLowerCase(), vnode.props[key])
}
}
// 递归处理 children
if (typeof vnode.children === 'string') {
// 是字符串,说明是文本节点,直接添加到父元素
el.appendChild(document.createTextNode(vnode.children))
} else if (Array.isArray(vnode.children)) {
// 遍历处理children子节点
vnode.children.forEach((child) => renderer(child, el))
}
// 将元素添加到挂在点下
container.appendChild(el)
}
/**
* 渲染组件
* @param {*} vnode
* @param {*} container
*/
function mountComponent(vnode, container) {
// tag是组件对象,调用render方法得到渲染的内容(虚拟DOM)
const subtree = vnode.tag.render()
renderer(subtree, container)
}
/** 组件虚拟DOM */
const MyConpoment = {
render() {
return {
tag: 'div',
props: {
onClick: () => {
alert('hello')
}
},
children: 'click me component'
}
}
}
const vnode = {
tag: MyConpoment
}
renderer(vnode, document.body)