Vue推荐在绝大多数情况下使用template来创建你的Html,然而在一些场景中,你真的需要JavaScript的完全编程的能力,这就是、render函数,它比template更接近编译器 使用template例子
<body class="">
<div id="example">
<my-component v-bind:level="1">hello</my-component>
</div>
<script src="js/vue.js"></script>
<script type="text/x-template" id="anchored-heading-template">
<h1 v-if="level===1">
<slot></slot>
</h1>
<h2 v-else-if="level===2">
<slot></slot>
</h2>
<h3 v-else-if="level===3">
<slot></slot>
</h3>
</script>
<script>
Vue.component("my-component", {
template: "#anchored-heading-template",
props: {
level: {
//type: Number,也可不加
//required: true
}
}
})
var app = new Vue({
el: "#example"
})
</script>
</body>
在这种场景中使用template并不是最好的选择:首先代码冗长,为了在不同级别的标题中插入锚点元素,我们需要重复的使用<slot></slot> 虽然模板在大多数组件中都非常好用,但是在这里它就不是那么简洁了,那么我们来尝试使用render函数重写上面的例子
<body class="">
<div id="example">
<my-component v-bind:level="2">HELO</my-component>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component("my-component", {
render: function(createElement) {
return createElement("h" + this.level, this.$slots.default)
},
props: {
level: {
//type: Number,
//required: true
}
}
})
var app = new Vue({
el: "#example"
})
</script>
</body>
这样代码简单清晰多了,在这个例子中,你需要知道当你不使用slot属性向组件中传递内容时,如上面的"hello world",这些子元素被存储在组件实例的$slots.default
结点、树、以及虚拟DOM
<body class="">
<div id="example">
<my-component v-bind:level="1">
Helloworld!
<h2>
woqu
<h3>what</h3>
</h2>
</my-component>
</div>
<script src="js/vue.js"></script>
<script>
var getChildrenTextContent = function(children) {
return children.map(function(node) {
return node.children ? getChildrenTextContent(node.children) : node.text
}).join("")
}
Vue.component("my-component", {
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
}
}
})
var app = new Vue({
el: "#example"
})
</script>
</body>
组件树中所有VNodes必须是唯一的,这意味着,下面的render function是无效的 render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // 错误-重复的VNodes myParagraphVNode, myParagraphVNode ]) }
如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如:下面这个例子render函数 完美的渲染了10个重复的段落 其中{length: 10}理解为Array like,即类数组对象(包含length属性)。
<body class="">
<div id="example">
<my-component></my-component>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component("my-component",{
render:function(createElement){
return createElement("div",
Array.apply(null,{length:10}).map(function(){
return createElement("p","hi")
}))
}
})
var app= new Vue({
el:"#example"
})
</script>
</body>
使用JavaScript代替模板功能,由于使用原生的JavaScript来实现某些东西很简单,Vue的render函数没有提供专用的API,比如,template中的v-if和v-for
<body class="">
<div id="example">
<my-component></my-component>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component("my-component", {
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.")
}
},
data: function() {
return {
items: [
{ name: "lily" },
{ name: "join" },
{ name: "jack" }
]
}
}
})
var app = new Vue({
el: "#example",
})
</script>
</body>
也可以
<body class="">
<div id="example">
<my-component v-bind:items="items"></my-component>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component("my-component", {
props:["items"],
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.")
}
},
})
var app = new Vue({
el: "#example",
data:{
items: [
{ name: "lily" },
{ name: "join" },
{ name: "jack11" }
]
}
})
</script>
</body>
render函数中没有与v-model相应的API,你必须自己来实现相应的逻辑
<body class="">
<div id="app">
<anchored-heading v-bind:value="data" v-on:input="show($event)"></anchored-heading>
{{data}}
</div>
<script src="js/vue.js"></script>
<script>
Vue.component('anchored-heading', {
props: ["value"],
render: function(createElement) {
var self = this;
return createElement("input", {
domProps: {
value: self.value
},
on: {
input: function(event) {
self.$emit('input', event.target.value)
}
}
})
},
})
var app = new Vue({
el: '#app',
data: {
data: "hello"
},
methods: {
show: function(a) {
this.data = a
console.log(a)
}
}
})
</script>
</body>
你可以从this.$slots获取VNodes列表中的静态内容
<body class="">
<div id="example">
<my-component>
<h2>hello slot</h2>
<span>spantext</span>
</my-component>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component("my-component", {
render: function(createElement) {
return createElement("div", this.$slots.default)
}
})
var app = new Vue({
el: "#example",
})
</script>
</body>
渲染成
<div id="example">
<div>
<h2>hello slot</h2> <span>spantext</span>
</div>
</div>
还可以从this.$scopedSlots中获得能用作函数的作用域插槽,这个函数返回VNodes:
<body class="">
<div id="example">
<my-component v-bind:msg="msg">
<template scope="props">{{props.text}}</template>
</my-component>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component("my-component", {
props: ["msg"],
render: function(createElement) {
return createElement("div", [this.$scopedSlots.default({
text: this.msg + "lily"
})])
}
})
var app = new Vue({
el: "#example",
data: {
msg: "this is the text"
}
})
</script>
</body>
渲染为
<div id="example">
<div>this is the textlily</div>
</div>
如果要用渲染函数向子组件中传递作用域插槽,可以利用 VNode 数据中的 scopedSlots
域:
<body class="">
<div id="app">
<ele>
</ele>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component('ele', {
render: function(createElement) {
return createElement('div', [
createElement('child', {
scopedSlots: {
default: function(props) {
return [
createElement('div', '来自父组件'),
createElement('span', props.text)
];
}
}
})
]);
}
});
Vue.component('child', {
render: function(createElement) {
return createElement('b', this.$scopedSlots.default({text: '我是组件'}));
}
});
new Vue({
el: '#app'
});
</script>
</body>
渲染成
<div id="app">
<div><b><div>来自父组件</div><span>我是组件</span></b></div>
</div>
函数化组件 之前创建的锚点标题组件比较简单,没有管理或者监听任何传递给他的信息,也没有生命周期方法,它只是一个接收参数的函数 在这个例子中,我们标记组件为functional,这意味它是无状态(没有data),无实例(没有this上下文),一个函数化组件就像这样 Vue.component('my-component', { functional: true, // 为了弥补缺少的实例 // 提供第二个参数作为上下文 render: function (createElement, context) { // ... }, // Props 可选 props: { // ... } })
注意:在2.3.0之前的版本中,如果一个函数式组件想要接受props,则props选项是必须的。在2.3.0或者以上的版本中,你可以省略props选项,所有组件上的属性都会被自动解析为props 组件需要的一切都是通过上下文传递,包括: props:提供props的对象 children:VNode子节点的数组 slot:slots对象 data:传递给组件的data对象 parent:对父组件的引用 listeners:(2.3.0+)一个包含了组件上所注册的v-on诊听器的对象。这只是一个指向data.on的别名 injections:(2.3.0+)如果使用了inject选项,则该对象包含了应该被注入的属性
在添加functional:true之后,锚点标题组件的render函数之间简单更新增加context参数,this.$slots.default更新为context.children,之后this.level更新为context.props.level 因为函数化组件只是一个函数,所以渲染开销也低很多。然而,对持久化实例的缺乏也意味着函数化组件不会出现在Vue devtools的组件树里,在作为包装组件时它们也同样非常有用,比如,当你需要做这些时 程序化地在多个组件中选择一个,再将children,props,data传递给子组件之前操作它们 下面是一个依赖传入props的值的smart-list组件例子,它能代表更多具体的组件
<body>
<div id="example">
<smart-item v-bind:data="data"></smart-item>
<button v-on:click="change('img')">显示图片</button>
<button v-on:click="change('video')">显示视频</button>
<button v-on:click="change('text')">显示文本</button>
</div>
<script src="js/vue.js"></script>
<script>
//图片组件选项
var ImgItem = {
props: ["data"],
render: function(createElement) {
return createElement("div", [
createElement("p", "图片组件"),
createElement("img", {
attrs: {
src: this.data.url
}
})
])
}
}
//视频组件
var VideoItem = {
props: ["data"],
render: function(createElement) {
return createElement("div", [
createElement("p", "视频组件"),
createElement("video", {
style: {
width: '500px',
},
attrs: {
src: this.data.url,
controls: "controls",
autoplay: "autoplay"
},
})
])
}
}
//纯文本组件
var TextItem = {
props: ["data"],//2、接收1传过来的props对象
render: function(createElement) {
return createElement("div", [
createElement("p", "这是一个文本组件"),
createElement("div", this.data.content)
])
}
}
Vue.component("smart-item", {
functional: true,
render: function(createElement, context) {
function getComponent() {
var data = context.props.data;
if (data.type === "img") return ImgItem;
if (data.type === "video") return VideoItem;
return TextItem
}
return createElement(
getComponent(), {
props: {
data: context.props.data //1、用于传给子组件 如:TextItem
}
},
context.children
)
},
//props选项在2.3.0及以上版本可以省略,
props: {
data: {
type: Object,
required: true
}
}
})
var app = new Vue({
el: "#example",
data: {
data: {}
},
methods: {
change: function(type) {
if (type === "img") {
this.data = {
type: "img",
url: "https://raw.githubusercontent.com/iview/iview/master/assets/logo.png"
}
} else if (type === "video") {
this.data = {
type: "video",
url: "http://vjs.zencdn.net/v/oceans.mp4"
}
} else if (type === "text") {
this.data = {
type: "text",
content: "这是一段纯文本"
}
}
}
},
created: function() {
this.change("img")
}
})
</script>
</body>
<script>
Vue.component('choice', {
template: '<div><ul><slot></slot></ul></div>'
});
Vue.component('item', {
functional: true,
render: function(h, context) {
return h('li', {
on: {
click: function() {
console.log(context.children);
console.log(context);
console.log(context.parent);
console.log(context.props)
}
}
}, context.children)
},
props: ['value']
})
new Vue({
el: '#container',
data: {
msg: 'hello'
}
});
</script>
最后渲染成
<ul><li>test</li></ul>
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接收,让其它组件去处理
(adsbygoogle = window.adsbygoogle || []).push({});