基础
Vue 推荐使用在绝大多数情况下使用 template 来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数 ,它比 template 更接近编译器。
<h1 >
<a name ="hello-world" href ="#hello-world" >
Hello world!
</a >
</h1 >
在 HTML 层, 我们决定这样定义组件接口:
<anchored-heading :level ="1" > Hello world!
</anchored-heading >
当我们开始写一个通过 level
prop 动态生成heading 标签的组件,你可很快能想到这样实现:
<script type ="text/x-template" id ="anchored-heading-template" >
<div >
<h1 v-if ="level === 1" >
<slot >
</slot >
</h1 >
<h2 v-if ="level === 2" >
<slot >
</slot >
</h2 >
<h3 v-if ="level === 3" >
<slot >
</slot >
</h3 >
<h4 v-if ="level === 4" >
<slot >
</slot >
</h4 >
<h5 v-if ="level === 5" >
<slot >
</slot >
</h5 >
<h6 v-if ="level === 6" >
<slot >
</slot >
</h6 >
</div >
</script >
Vue.component(
'anchored-heading' , {
template :
'#anchored-heading-template' ,
props : {
level : {
type :
Number ,
required :
true
}
}
})
在这种场景中使用 template 并不是最好的选择:首先代码冗长,为了在不同级别的标题中插入锚点元素,我们需要重复地使用 <slot></slot>
。其次由于组件必须有根节点,标题和锚点元素被包裹在了一个无用的 div
中。
虽然模板在大多数组件中都非常好用,但是在这里它就不是很简洁的了。那么,我们来尝试使用 render
函数重写上面的例子:
Vue.component(
'anchored-heading' , {
render :
function (createElement ) {
return createElement(
'h' +
this .level,
this .$slots.default
)
},
props : {
level : {
type :
Number ,
required :
true
}
}
})
简单清晰很多!简单来说,这样代码精简很多,但是需要非常熟悉 Vue 的实例属性。在这个例子中,你需要知道当你不使用 slot
属性向组件中传递内容时,比如 anchored-heading
中的 Hello world!
, 这些子元素被存储在组件实例中的 $slots.default
中。如果你还不了解, 在深入 render 函数之前推荐阅读 instance properties API 。
createElement
参数
第二件你需要熟悉的是如何在 createElement
函数中生成模板。这里是 createElement
接受的参数:
createElement(
'div' ,
{
},
[
createElement(
'h1' ,
'hello world' ),
createElement(MyComponent, {
props : {
someProp :
'foo'
}
}),
'bar'
]
)
完整数据对象
有一件事要注意:在 templates 中,v-bind:class
和 v-bind:style
,会有特别的处理,他们在 VNode 数据对象中,为最高级配置。
{
'class' : {
foo :
true ,
bar :
false
},
style: {
color :
'red' ,
fontSize :
'14px'
},
attrs: {
id :
'foo'
},
props: {
myProp :
'bar'
},
domProps: {
innerHTML :
'baz'
},
on: {
click :
this .clickHandler
},
nativeOn: {
click :
this .nativeClickHandler
},
directives: [
{
name :
'my-custom-directive' ,
value :
'2'
expression:
'1 + 1' ,
arg :
'foo' ,
modifiers : {
bar :
true
}
}
],
scopedSlots: {
default :
props => createElement(
'span' , props.text)
},
slot:
'name-of-slot'
key:
'myKey' ,
ref :
'myRef'
}
完整示例
有了这方面的知识,我们现在可以完成我们最开始想实现的组件:
var getChildrenTextContent =
function (children ) {
return children.map(
function (node ) {
return node.children
? getChildrenTextContent(node.children)
: node.text
}).join(
'' )
}
Vue.component(
'anchored-heading' , {
render :
function (createElement ) {
var headingId = getChildrenTextContent(
this .$slots.default)
.toLowerCase()
.replace(
/\W+/g ,
'-' )
.replace(
/(^\-|\-$)/g ,
'' )
return createElement(
'h' +
this .level,
[
createElement(
'a' , {
attrs : {
name : headingId,
href :
'#' + headingId
}
},
this .$slots.default)
]
)
},
props : {
level : {
type :
Number ,
required :
true
}
}
})
约束
VNodes 必须唯一
所有组件树中的 VNodes 必须唯一。这意味着,下面的 render function 是无效的:
render:
function (createElement ) {
var myParagraphVNode = createElement(
'p' ,
'hi' )
return createElement(
'div' , [
myParagraphVNode, myParagraphVNode
])
}
如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这个例子 render 函数完美有效地渲染了 20 个重复的段落:
render:
function (createElement ) {
return createElement(
'div' ,
Array .apply(
null , {
length :
20 }).map(
function ( ) {
return createElement(
'p' ,
'hi' )
})
)
}
使用 JavaScript 代替模板功能
v-if
and v-for
无论什么都可以使用原生的 JavaScript 来实现,Vue 的 render 函数不会提供专用的 API。比如, template 中的 v-if
和 v-for
:
<ul v-if ="items.length" >
<li v-for ="item in items" > {{ item.name }}
</li >
</ul >
<p v-else > No items found.
</p >
这些都会在 render 函数中被 JavaScript 的 if
/else
和 map
重写:
render:
function (createElement ) {
if (
this .items.length) {
return createElement(
'ul' ,
this .items.map(
function (item ) {
return createElement(
'li' , item.name)
}))
}
else {
return createElement(
'p' ,
'No items found.' )
}
}
v-model
在render函数中,没有提供v-model
的实现,所以你需要自己实现逻辑:
render:
function (createElement ) {
var self =
this
return createElement(
'input' , {
domProps : {
value : self.value
},
on : {
input :
function (event ) {
self.value = event.target.value
self.$emit(
'input' , event.target.value)
}
}
})
}
This is the cost of going lower-level, but it also gives you much more control over the interaction details compared to v-model
.
Event & Key Modifiers
对于 .capture
和 .once
这样的事件修饰符, Vue 提供了用于 on
的前缀:
Modifier(s) Prefix .capture
!
.once
~
.capture.once
or .once.capture
~!
示例:
on: {
'!click' :
this .doThisInCapturingMode,
'~keyup' :
this .doThisOnce,
`~!mouseover` :
this .doThisOnceInCapturingMode
}
对于其他的事件和关键字修饰符, 你可以在处理程序中使用事件方法实现:
Modifier(s) Equivalent in Handler .stop
event.stopPropagation()
.prevent
event.preventDefault()
.self
if (event.target !== event.currentTarget) return
Keys: .enter
, .13
if (event.keyCode !== 13) return
(change 13
to another key code for other key modifiers) Modifiers Keys: .ctrl
, .alt
, .shift
, .meta
if (!event.ctrlKey) return
(change ctrlKey
to altKey
, shiftKey
, or metaKey
, respectively)
这里是所有修饰符一起使用的例子:
on: {
keyup :
function (event ) {
if (event.target !== event.currentTarget)
return
if (!event.shiftKey || event.keyCode !==
13 )
return
event.stopPropagation()
event.preventDefault()
}
}
Slots
使用this.$slots
访问静态插槽内容作为 VNode 数组:
render:
function (createElement ) {
return createElement(
'div' ,
this .$slots.default)
}
使用this.$scopedSlots
访问作用域插槽作为返回VNodes的函数:
render:
function (createElement ) {
return createElement(
'div' , [
this .$scopedSlots.default({
text :
this .msg
})
])
}
使用render函数传递作用域插槽到子组件,使用VNode数据中的scopedSlots
关键字:
render (createElement) {
return createElement(
'div' , [
createElement(
'child' , {
scopedSlots: {
default :
function (props ) {
return createElement(
'span' , props.text)
}
}
})
])
}
JSX
如果你写了很多 render
函数,可能会觉得痛苦:
createElement(
'anchored-heading' , {
props : {
level :
1
}
}, [
createElement(
'span' ,
'Hello' ),
' world!'
]
)
特别是模板如此简单的情况下:
<anchored-heading :level ="1" >
<span > Hello
</span > world!
</anchored-heading >
这就是会有一个 Babel plugin 插件,用于在 Vue 中使用 JSX 语法的原因,它可以让我们回到于更接近模板的语法上。
import AnchoredHeading
from
'./AnchoredHeading.vue'
new Vue({
el :
'#demo' ,
render (h) {
return (
<AnchoredHeading level ={1} >
<span > Hello
</span > world!
</AnchoredHeading >
)
}
})
将 h
作为 createElement
的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的,如果在作用域中 h
失去作用, 在应用中会触发报错。
更多关于 JSX 映射到 JavaScript,阅读 使用文档 。
函数化组件
之前创建的锚点标题组件是比较简单,没有管理或者监听任何传递给他的状态,也没有生命周期方法。它只是一个接收参数的函数。 在这个例子中,我们标记组件为 functional
, 这意味它是无状态(没有 data
),无实例(没有 this
上下文)。 一个 函数化组件 就像这样:
Vue.component(
'my-component' , {
functional :
true ,
render:
function (createElement, context ) {
},
props: {
}
})
组件需要的一切都是通过上下文传递,包括:
props
: 提供props 的对象children
: VNode 子节点的数组slots
: slots 对象data
: 传递给组件的 data 对象parent
: 对父组件的引用
在添加 functional: true
之后,锚点标题组件的 render 函数之间简单更新增加 context
参数,this.$slots.default
更新为 context.children
,之后this.level
更新为 context.props.level
。
函数化组件只是一个函数,所以渲染开销也低很多。但同样它也有完整的组件封装,你需要知道这些, 比如:
Since functional components are just functions, they’re much cheaper to render. However, this also mean that functional components don’t show up in VueJS Chrome dev tools component tree.
They’re also very useful as wrapper components. For example, when you need to:
下面是一个依赖传入 props 的值的 smart-list
组件例子,它能代表更多具体的组件:
var EmptyList = {
}
var TableList = {
}
var OrderedList = {
}
var UnorderedList = {
}
Vue.component(
'smart-list' , {
functional :
true ,
render :
function (createElement, context ) {
function appropriateListComponent ( ) {
var items = context.props.items
if (items.length ===
0 )
return EmptyList
if (
typeof items[
0 ] ===
'object' )
return TableList
if (context.props.isOrdered)
return OrderedList
return UnorderedList
}
return createElement(
appropriateListComponent(),
context.data,
context.children
)
},
props : {
items : {
type :
Array ,
required :
true
},
isOrdered :
Boolean
}
})
slots()
和 children
对比
你可能想知道为什么同时需要 slots()
和 children
。slots().default
不是和 children
类似的吗?在一些场景中,是这样,但是如果是函数式组件和下面这样的 children 呢?
<my-functional-component >
<p slot ="foo" >
first
</p >
<p > second
</p >
</my-functional-component >
对于这个组件,children
会给你两个段落标签,而 slots().default
只会传递第二个匿名段落标签,slots().foo
会传递第一个具名段落标签。同时拥有 children
和 slots()
,因此你可以选择让组件通过 slot()
系统分发或者简单的通过 children
接收,让其他组件去处理。
模板编译
你可能有兴趣知道,Vue 的模板实际是编译成了 render 函数。这是一个实现细节,通常不需要关心,但如果你想看看模板的功能是怎样被编译的,你会发现会非常有趣。下面是一个使用 Vue.compile
来实时编译模板字符串的简单 demo:
render:
function anonymous() {
with(this){return _c('div',[_m(0),(message)?_c('p',[_v(_s(message))]):_c('p',[_v("No message.")])])}
}
staticRenderFns:
_m(0): function anonymous() {
with(this){return _c('header',[_c('h1',[_v("I'm a template!")])])}
}
原文: http://vuejs.org/guide/render-function.html
所有评论(0)