0. vue是如何将模板渲染到页面的
假设我们有如下模板
<div id="foo" :class="cls"></div>
vue的编译器会把这段代码编译成渲染函数:
render() {
// return h('div', {id: "foo", class: cls})
return {
tag: 'div',
props: {
id: 'foo',
class: cls
},
patchFlags: 1 // 假设数字1代表class是动态的
}
}
然后渲染器再把渲染函数返回的虚拟DOM渲染为真实的DOM。
function renderer(vnode, container) {
const el = document.createElement(vnode.tag);
for (const key in vnode.props) {
if (/^on/.test(key)) {
// 以on开头说明它是事件
el.addEventListener(
key.substring(2).toLowerCase(),
vnode.props[key]
)
}
}
if (typeof vnode.children === 'string') {
el.appendChild(document.createTextNode(vnode.children))
}
else if (Array.is(vnode.children)) {
vnode.children.forEach(child => renderer(child, el));
}
container.appendChild(el);
}
编辑器与渲染器是vuejs的核心组成部分,下面开始逐渐深入讲解。
1. 声明式的描述UI
vue是一个声明式的UI框架,即用户再使用vue开发页面时是声明式的描述UI的。
模板声明式描述UI
<div class="cls"><span>hello world</span></div>
js对象声明式描述UI
const title = {
tag: 'h1',
props: {
class: 'title',
onclick: handler
},
children: [
{
tag: 'span'
}
]
}
js对象即虚拟DOM。虚拟DOM描述UI更加灵活,但模板更加直观。
小知识点: h函数是一个辅助创建虚拟dom的工具函数,render函数中调用h函数生成了一个虚拟dom对象。
2. 初识渲染器
渲染器的作用: 就是把虚拟DOM渲染成真实DOM。
h('div', 'htllo') ===> 渲染器 ===> 真实DOM
举例:
// 虚拟DOM
const vnode = {
tag: 'div', // 描述标签名称
props: { // 描述标签的属性事件等内容
class: 'cls',
onClick: () => alert('hello')
},
children: [ // 描述子节点
'click me'
]
}
该虚拟节点通过上文的renderer函数,渲染为真实的DOM。
renderer(vnode, document.body); // body 为真实DOM元素,作为虚拟DOM挂载点。
renderer实现思路
-
创建元素
-
为元素添加属性和事件
-
处理children
-
挂载到真实DOM节点
渲染器的工作原理很简单,就是利用一些DOM操作API来完成渲染工作。
组件的本质
vue组件就是一组DOM元素的封装。可以定义一个函数来代表组件,函数的返回值就代表组件要渲染的内容。
const MyComponent = () => {
return {
tag: 'div',
props: {
onClick: () => alert('hello')
},
children: [
'click me'
]
}
}
组件的返回值也是一个虚拟dom,他代表了组件要渲染的内容。
用虚拟DOM描述组件,利用对象中的tag值
cpnst vnode = {
tag: MyComponent
}
渲染器为了能渲染组件,需要做出修改:
function renderer(vnode, container) {
if (typeof vnode.tag === 'string') {
// 虚拟dom是标签元素
mountElement(vnode, container);
}
else if (typeof vnode.tag === 'function') {
// 说明vnode描述的是组件
mountComponent(vnode, container)
}
}
function mountComponent(vnode, container) {
const subtree = vnode.tag()
renderer(subtree, container)
}
也可以用对象描述组件,渲染函数只需修改 typeof vnode.tag === ‘object’。
模板的工作原理
文章开头已经介绍了模板渲染到页面的原理,核心是编译器和渲染器配合完成。
针对一个vue组件(.vue文件就是一个组件)
<template>
<div @click="handler">
click me
</div>
</template>
<script>
export default {
data() {}
methods: {
handler: () => {}
}
}
</script>
编译器会把模板内容编译成渲染函数并添加到script标签块的组件对象上
// 最中浏览器里运行的代码如下
<script>
export default {
data() {}
methods: {
handler: () => {}
},
render() {
return h('div', {onClick: handler}, 'click me')
}
}
</script>
由此也能看出,vuejs是一个编译时+运行时的框架。
备注:结合《VUE.js设计与实现》深入学习vuejs,记录学习过程。