3.1 声明式的描述UI
如何声明式的描述DOM元素、属性、事件、元素层级结构?
(1)
使用与 HTML 标签一致的方式来描述 DOM 元素,比如一个div标签<div></div>
使用v-on来描述事件,比如点击事件<div @click=“handler”></div>
以上可以看到,用户不需要手写命令式代码,这就是声明式的描述UI
(2)
使用js对象来描述UI,比如以下代码:
const title = {
tag: ‘h1’, // 标签名
props: {
onClick: handler // 标签属性
},
children: [
{ tag: ‘span’ } // 子节点
]
}
// 以上相当于:
<h1 @click=“handler”><span></span></h1>
此时我们可能会有疑问:以js的方式难道不是比用模版更麻烦吗?
回答:使用js描述UI会更加灵活
举例:根据标题的不同分别展示不同的样式,通过h1~h6表示
// 使用js描述UI
let level = 3
const title = {
tag: ‘h${level}’
}
// 使用模版描述UI
<h1 v-if=“level === 1”></h1>
<h2 v-else-if=“level === 2”></h2>
<h3 v-else-if=“level === 3”></h3>
<h4 v-else-if=“level === 4”></h4>
<h5 v-else-if=“level === 5”></h5>
<h6 v-else-if=“level === 6”></h6>
由此可见,模版描述远没有js描述更加灵活,但模板描述比js对象描述更加直观。而使用js对象来描述UI的方式,就是所谓的虚拟DOM。
其实我们在 Vue.js 组件中手写的渲染函数就是使用虚拟 DOM 来描述 UI 的,例如:
import { h } from ‘vue’
export default {
render () {
return h(‘h1’, { onClick: handler }) // 虚拟DOM
}
}
// h 函数就是一个辅助创建虚拟 DOM 的工具函数
3.2 初识渲染器
渲染器的作用就是把虚拟DOM渲染为真实DOM。
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.substring(2).toLowerCase(), // 事件名称小写化-得到合法的事件名称
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)
}
const vnode = {
tag: 'div',
props: {
onClick: () => alert('hello')
},
children: 'click me'
}
this.renderer(vnode, document.body)
3.3 组件的本质
组件就是一组DOM元素的封装 , 这组DOM元素就是组件要渲染的内容
const MyCompontent = () => {
return {
tag: ‘div’,
props: {
onClick: () => alert('hello'),
children: ‘click me’
}
}
// 那么类似3.2 中的代码渲染用于组件渲染的实现就很容易了:
renderer (vnode, container) {
if (typeof vnode.tag === ’string’) {
mountElement(vnode, container) // 这里的mountElement就是上文的renderer()函数
} else if (typeof vnode.tag === ‘function’) {
mountCompent(vnode, container)
}
}
// mountCompent函数的实现:
mountCompent (vnode, container) {
// 调用组件函数,获取组件要渲染的内容(虚拟DOM)
const subtree = vnode.tag()
// 递归的调用renderer 渲染subtree
renderer(vnode, container)
}
举一反三:
MyCompontent可以是一个对象,使用render返回的对象,那么在renderer中判断类型是就要把function 修改为 object,在mountCompent函数中,获取subtree,需要修改为vnode.tag.render()
3.4 模版的工作原理
编译器-编译器的作用是将模版编译为渲染函数 可参考3.1
结论:
无论是使用模版还是渲染函数,对于组件来说,它要渲染的内容都是通过渲染函数产生的然后渲染器再把渲染函数返回的虚拟DOM渲染为真实DOM,就是Vue.js渲染页面的流程。
3.5 Vue.js是各个模块组成的有机整体
组件的实现依赖渲染器,模版的编译依赖编译器,并且编译后的代码是根据渲染器和虚拟DOM的设计决定的。