9.Render函数
9.1 什么是 Virtrual Dom
Virtual Dom 是基于 JavaScript 计算的,在状态发生变化时,Virtual Dom 会进行 Diff 运算。

正常的 DOM 节点在 HTML 中:
<div id="main">
<p>文本内容</p>
</div>
<p>文本内容</p>
用 Virtual Dom 创建的 JavaScript 对象一般会是这样的:
var vNode = {
tag: 'div',
attributes:{
id: 'main'
},
children:{
//p 节点
}
}
Virtual Dom 就是通过一种 VNode 类表达的,每个 DOM 元素或组件都对应一
个 VNode 对象。(关键字: tag,data,childred,text...)
VNode 主要分为如下几类:
- TextVNode 文本节点
- ElementVNode 普通元素节点
- ComponentVNode 组件节点
- EmptyVNode 没有内容的注释节点
- CloneVNode 克隆节点,可以是以上任意类型的节点,唯一的区别在于 isCloned 属性为 true 。
多数场景使用 template 就足够,但在一些特定的场景下,使用 Virtual Dom 会更简单。
9.2 什么是 Render 函数
实现锚点标签
- 使用 template 实现
div#app
anchor[level="2"][title="特性"]{特性}
script[text/x-template][id="anchor"]
div
(h$[v-if="level===$"][:id="title"]>a[:href="'#'+title"]>slot )*6
script
Vue.component("anchor",{
props:{
title:{
type:String,
default:''
},
level:{
type:Number,
required:true
}
}
});
var app = new Vue({
el:'#app'
})
- 使用 Render 实现
div#app
anchor[level="2"][title="特性"]{特性}
script
Vue.component('anchor',{
props:{
level:{
type:Number,
required:true
},
title:{
type:String,
default:''
}
},
render:funciton(createElement){
return createElement(
'h'+this.level,
{
attrs:{
id:this.title
}
},
[
createElement(
'a',
{
domProps:{
href:'#'+this.title
}
},
this.$slots.default
)
]
)
}
})
9.3 reateElement 用法
9.3.1 基本参数
createElement 构成了 Vue Virtual Dom 的模板,它有 3 个参数:
createElement(
// {String | Object | Function)
//一个 HTML 标签,组件选项,或一个函数
//必须 Return 上述其中一个
'div',
//{Object}
// 一个对应属性的数据对象,可选
//可以在 template 中使用
{
//和 v-bind:class 一样的 API
class:{
foo : true ,
bar: false
},
//和 v-bind:style 一样的 API
style : {
color:'red',
fontSize:'14px'
},
//正常的 HTML 特性
attrs: {
id:'foo'
},
//组件 props
props: {
myProp : 'bar'
},
//DOM 属性
domProps: {
InnerHTML :'baz'
},
//自定义事件监听器"on"
//不支持如 v-on:keyup.enter 的修饰器
//需要手动匹配 keyCode
on: {
click: this.clickHandler
},
//仅对于组件,用于监听原生事件
//而不是组件使用 vm.$emit 触发的自定义事件
nativeOn: {
click: this.nativeClickHandler
},
//自定义指令
directives: [
name:'my-custom-directive',
value:'2'
expression:'1 + 1',
arg : 'foo',
modifiers : {
bar: true
}
},
//作用域 slot
//{ name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => h('span', props text)
},
//如果子组件有定义 slot 的名称
slot:'name-of-slot'
//其他特殊顶层属性
key: 'myKey',
ref: 'myRef'
},
// {String | Array)
// 子节点( VNodes ),可选
[
createElement ( 'h1','hello world'),
createElement(MyComponent, {
props : {
someProp:'foo'
}
}),
'bar'
]
)
第一个参数必选,可以是一个 HTML 标签,也可以是一个组件或函数;第二个是可选参数,数据对象,在 template 中使用。第三个是子节点,也是可选参数,用法一致。
9.3.2 约束
所有组件树中,如果 VNode 是组件或含有组件的 slot ,那么 VNode 必须唯一。
错误的写法:
- 重复使用组件
script
//局部声明组件
var Child={
render:function(createElement){
return createElement('p','text');
}
};
Vue.component('ele',{
render:function(createElemnt){
//使用组件 Child, 创建一个子节点
var ChildNode=createElement(Child);
return createElement('div',[
ChildNode,
ChildNode
])
}
})
//-----
//改
Array.apply(null,{length:2}).map(function(){
return createElement(Child)
})
- 重复使用含有组件的 slot
<- div#app>ele>div>Child ->
//全局注册组件
Vue.component('Child',{
render:function(createElement{
return createElement('p','text');
}
});
Vue.component('ele',{
render:function(createElement){
return createElement('div',{
this.$slots.default,
this.$slots.default
})
}
})
上面两个例子中的两个 Child 组件都只能渲染出一个,VNode受到了约束。
如何重复使用组件,或含有组件的 slot。
代码在文件夹 9.3.2 约束 中
通过一个循环和工厂函数可以渲染5个重复的子组件 Child。 p145
深度克隆子节点
9.3.3 使用 JavaScript 代替模板功能
在 Render 函数中,不能使用 Vue 内置的指令,所以可以用原生的 JavaScript 实现。
v-if | v-elsev-formapv-model修饰符
| 修饰符 | 对应的句柄 |
|---|---|
.stop | event.stopPropagation() |
.prevent | event.preventDefault() |
.self | if(event.target!==event.currentTarget) return |
.enter、.13 | if(event.keyCode!==13) return 替换 13 位需要的 keyCode |
.ctrl、.alt、.shift、.meta | if(!event.ctrlKey) return 根据需要替换 ctrlKey 为 altKey、shiftKey 或 metaKey。 |
| 修饰符 | 特殊的前缀(写在 on 里) |
|---|---|
.capture | ! |
.once | ~ |
.capture.once 或 .once.capture | ~! |
//写在 on 前
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
'~!mouseover':this.doThisOnceInCapturingMode
}
slot
9.4 函数化组件
作用:减少开销渲染
做法:Render 提供两个参数:
function参数,值为true,使组件无状态和无实例(没有datathis上下文),这样 render 函数返回的虚拟节点更容易渲染。context参数,提供临时上下文,组件需要的data、props、slots、children、parent都是通过这个上下文来传递。(比如:this.level => contex.props.level)
根据 button 选择不同组件的场景
div#app
smart-item[:data="data"]
(button[@click="img|video|text"]{切换为。。}) *3
var ImgItem={
props:['data'],
render:function(createElement){
return createElement('div',[
createElement('p','图片组件'),
createElement('img',{
attrs:{
src:this.data.url
}
})
])
}
};
var VideoItem={
props:[...],
render:function(createElement){
...
}
};
var TextItem={
//这个 data_____ 要和 component 的 data_____ 一样
props:['data_____'],
render:function(createElement){
...
}
};
Vue.component('smart-item',{
functional:true,
render:function(createElement,context){
function getComponent(){
//根据 context.props.data
if (...) return ...Item
}
return createElement(){
getComponent(),
{
props:{
//把 smart-item 的 prop:data 传给上面只能选择的组件
data_____: context.props.data
}
},
context.children
}
}
});
var app=Vue({
el:'#app',
data:{
data:{}
},
methods:{
change(type){
type==>data:{type:'...',url|content:'...'}
}
}
})
app定义了data=>传给了组件smart-item=>三个对象都接收一个 prop: data
函数化组件不常用,上例可以用组件的 is 特性来动态挂载。函数化组件主要适用于以下两个场景:
- 程序化的在多个组件中选择一个
- 在将 children,props,data 传递给子组件之前操作它们
9.5 JavaScript 的语法扩展:JSX
在模板比较简单时使用 Render 函数写起来会比较麻烦。解决:提供 插件babel-plugin-transroe-vue-jsx来支持 JSX 语法。
比较:
- template
<Anchor :level="1">
<span>一级</span>标题
</Anchor>
- Render -> createElement 改写
return createElement('Anchor',{
props:{
level:1
}
},[
createElement('span','一级'),
'标题'
])
- Render -> 使用 JSX 语法
// 需要在 webpack 里配置插件 bebel-plugin-tranform-vue-jsx 编译
// 参数 h 不能省略,否则使用时会触发错误
new Vue({
el:'#app',
render(h){
return (
<Anchor level={1}>
<span>一级</span>标题
</Anchor>
)
}
})
- 使用
createElement常用配置- 正常实现
render(createElement){ return createElement('div',{ props:{ text:'some text' }, attrs:{ is:'myDiv' }, domProps:{ innerHTML:'content' }, on:{ change:this.changeHandler }, nativeOn:{ click:this.clickHandler }, class:{ show:true, on:false }, style:{ color:'#fff', background:'#f50' }, key:'key', ref:'element', refInFor:true, slot:'slot' }) }- 使用 JSX
render(h){ return ( <div id="myDiv" domPropsInnerHTML="content" onChange={this.clickHandler} nativeOnClick={this.clickHandler} class={{show:true,on:false}} style={{color:'#fff',background:'#f50'}} key="key" ref="element" refInFor slot="slot"> </div> ) }- 注意:如果项目不是 JSX 强驱动,建议还是以模板 template 的方式为主,特殊场景(比如锚点标题)使用 Render 的 createElement 辅助。

浙公网安备 33010602011771号