一、声明式地描述UI
- 声明式:
<h1 @click=handler>click Me</h1>
- 渲染函数(返回虚拟DOM, 组件的渲染内容)
export default {
render() {
return {
tag: "h1",
props: { onClick: handler },
children: "click Me",
}
}
}
- 渲染函数 + h(h一个辅助创建虚拟DOM的工具函数)
h(type, props, children)
export default {
render() {
return h("h1", {onClick: handler}, "click Me")
}
}
二、初识渲染器
渲染器(renderer): 虚拟DOM →真实DOM
function renderer(vnode, container) {
const el = document.createElement(vnode.tag);
for(let key in vnode.props) {
if(/^on/.test(key)){
el.addEventListener(key.substr(2).toLowerCase(), vnode.props[key])
}
}
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);
}
let vnode = {
tag: "h1",
props: { onClick: () => alert("hello world") },
children: "click Me",
}
renderer(vnode, document.body)
三、组件的本质
组件就是一组DOM元素的封装(可以是一个返回虚拟DOM的函数, 也可能是一个包含render的对象)
const MyComponent = {
render () {
return {
tag: "h1",
props: { onClick: () => alert("hello world") },
children: "click Me",
}
}
}
const vnode = {
tag: MyComponent
}
function mountElement(vnode, container) {
const el = document.createElement(vnode.tag);
for(let key in vnode.props) {
if(/^on/.test(key)){
el.addEventListener(key.substr(2).toLowerCase(), vnode.props[key])
}
}
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);
}
function mountComponent(vnode, container) {
const subtree = vnode.tag.render();
renderer(subtree, container)
}
function renderer(vnode, container) {
if(typeof vnode.tag === "string") {
mountElement (vnode, container)
} else if(typeof vnode.tag === "object") {
mountComponent(vnode, container);
}
}
renderer(vnode, document.body)
四、模板的工作原理
编译器(compiler): 模板 (template) → 虚拟DOM
01 <template>
02 <div @click="handler">
03 click me
04 </div>
05 </template>
06
07 <script>
08 export default {
09 data() {/* ... */},
10 methods: {
11 handler: () => {/* ... */}
12 }
13 }
14 </script>
编译成
01 export default {
02 data() {/* ... */},
03 methods: {
04 handler: () => {/* ... */}
05 },
06 render() {
07 return h('div', { onClick: handler }, 'click me')
08 }
09 }
五、vue.js时是各个模块组成的有机整体
模板 (template)→ 编译 → 虚拟DOM → 渲染 → 真实的DOM
例子: 编译器能够识别模板中的动态属性和静态属性,生成虚拟DOM的时候附带信息, 使得渲染器能够减少寻找变更点的工作量
模板:
<div id="foo" :class=cls></div>
编译结果:
render() {
return {
tag: "div",
props: {
id: "foo",
class: cls
},
patchFlags: 1 //假设数字 1 代表 class是动态的
}
}