render 渲染函数
编译流程
template
通过 parse()
方法 -> ast
-> 通过 generate()
方法生成 render
函数 -> 通过 render
函数生成 VNode
-> patch()
方法生成真实 DOM
parse
:语法分析器,将template
生成AST
(抽象语法树)var ast = parse(template, options);
optimize
:对AST
进行优化处理,标记静态节点generate
:对AST
对象转为字符串形式的JS
代码
1. parse
函数
parse(template, options)
是将 template
模板语法编译为 ast
的函数。它的两个参数分别是:
template
就是.vue
文件中的模板字符串;options
就是实例化组件时的options
配置项,可以配置解析规则,如是否保留html
注释,模板语法分隔符规则等。
1.1 parseHTML
函数
parseHTML(html, options)
函数是 parse
函数的核心方法。
- 先遍历整个
html
字符串,使用正则将html
拆分为对象数组结构,等待createASTElement()
方法调用。 createASTElement(tag, attrs, parent)
方法是将拆分后的数组转为Ast
抽象语法树
2. generate
函数
generate(ast, options)
函数是将 Ast
语法树转换为 render
的方法。
function generate(ast, options) {
const state = new CodegenState(options)
const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
return {
render: `with(this){ return ${code} }` // 函数字符串,
staticRenderFns: state.staticRenderFns
}
}
function generate(ast, options) {
const state = new CodegenState(options)
const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
return {
render: `with(this){ return ${code} }` // 函数字符串,
staticRenderFns: state.staticRenderFns
}
}
2.1 genElement
函数
它会对传入的 ast
进行判断,不同类型的 Ast Element
会调用不同的方法并生成 render
,比如:
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state) // 静态节点,optimize 函数优化结果
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state) // v-once
} else if (el.for && !el.forProcessed) {
return genFor(el, state) // v-for
} else if (el.if && !el.ifProcessed) {
return genIf(el, state) // v-if
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0' // 子组件
} else if (el.tag === 'slot') {
return genSlot(el, state) // 插槽
}
// 还有个大的组件的处理
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state) // 静态节点,optimize 函数优化结果
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state) // v-once
} else if (el.for && !el.forProcessed) {
return genFor(el, state) // v-for
} else if (el.if && !el.ifProcessed) {
return genIf(el, state) // v-if
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0' // 子组件
} else if (el.tag === 'slot') {
return genSlot(el, state) // 插槽
}
// 还有个大的组件的处理
3. 生成 VNode
render
函数里面是一个 createElement
方法,它与模板生产的 render
中的 _c
是一样的。createElement
方法中主要有两个方法,一个是 VNode
构造函数,创建普通节点;一个是 createComponent
,创建组件节点。
3.1 VNode 构造函数
createElement
中的 VNode
构造函数,传入对应参数实例化后将返回虚拟节点,它主要有两个方法:
createEmptyVNode
:创建空节点createTextVNode
:创建虚拟文本节点
3.2 createComponent 方法
这个函数主要是提取出组件的 propsData
和 listeners
,并将其保存在 componentOptions
下。提取之后,VNodeData
中剩余的属性和事件属于 VNode
对应的 dom
元素。这种情况下我们想要给组件添加事件和属性时可能需要修改 vnode.componentOptions
下的 propsData
和 listeners
以及 vnode.data.attrs
,他们分别对应了组件内部的 $props,$listeners,$attrs
。
4. 生成真实 DOM
生成真实 dom
是从 _update
函数开始,之后调用 __patch__
方法,对虚拟 dom
进行 diff
算法比较优化,在数据处理后,调用真实 dom
的一些操作方法,比如 insertBefore
appendChild
等方法插入到真实的 dom
中。
关于 patch
方法,请看 diff
章节
完整过程伪代码实现
let ast = parse(template, options) // 将模板生成 ast 语法树
optimize(ast, options) // 标记静态节点,就是无需动态改变的标签
let code = generate(ast, options); // 将 ast 生成 render 函数字符串
let render = with(this){ return ${code} }; // with 方法是为了将 this 指向 vm 实例,在某种程度上实现对于作用域的动态注入
let renderFn = new Function(render); // 将 render 字符串实例化为 render 函数,这就是我们 vue 中的 render() 方法了
_update(renderFn) //
let ast = parse(template, options) // 将模板生成 ast 语法树
optimize(ast, options) // 标记静态节点,就是无需动态改变的标签
let code = generate(ast, options); // 将 ast 生成 render 函数字符串
let render = with(this){ return ${code} }; // with 方法是为了将 this 指向 vm 实例,在某种程度上实现对于作用域的动态注入
let renderFn = new Function(render); // 将 render 字符串实例化为 render 函数,这就是我们 vue 中的 render() 方法了
_update(renderFn) //
render
函数在平时写 vue
时可能极少使用到,一般我们都会使用 template
实现模板,但 template
模板,会在 beforeMount
生命周期中被编译为 render
函数。
像 elememt-ui
中的自定义 message-box
组件,就需要传入一个 render
函数
区别:
template
模板编写容易;render
函数灵活性高,性能更好(省去了编译步骤),优先级也更高;
语法:
/**
* @param { String | Object | Function } param1 html标签名称 | 组件 | 异步组件
* @param { Object } [attribute] 模板中 attribute 对应的数据对象、样式、事件等
* @param { String | Array } [VNodes] 文本节点或者 createElement() 创建的子节点
* */
createElement(param1, attribute, VNodes)
/**
* @param { String | Object | Function } param1 html标签名称 | 组件 | 异步组件
* @param { Object } [attribute] 模板中 attribute 对应的数据对象、样式、事件等
* @param { String | Array } [VNodes] 文本节点或者 createElement() 创建的子节点
* */
createElement(param1, attribute, VNodes)
// data 数据
data() {
return {
list: ['one', 'two', 'three']
}
}
// data 数据
data() {
return {
list: ['one', 'two', 'three']
}
}
- 模板写法:
<template>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</template>
<template>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</template>
createElement
写法:
render(createElement) {
return createElement('ul', {}, this.list.map(item => {
return createElement('li', {}, item)
}))
}
render(createElement) {
return createElement('ul', {}, this.list.map(item => {
return createElement('li', {}, item)
}))
}
JSX
写法:
render(h) {
return (
<ul>
{ list.map(item => <li>{item}</li>) }
</ul>
)
}
render(h) {
return (
<ul>
{ list.map(item => <li>{item}</li>) }
</ul>
)
}
语法
render(crateElmentFn,context){}
render(crateElmentFn,context){}