# render函数
# 什么是render函数
关于渲染函数render的基础及概念,可参考官方文档 (opens new window)。简单来说,render的核心,就是通过createElement (opens new window)(通常我们简写为h函数)函数来创建vnode,而不是模板的方式。
通常情况下,我们在编写vue组件时,都是基于模板的单文件组件的方式,这种方式结构清晰,简洁易懂,即下面这种:
//demo.vue
<template>
<div class="my-modal">
<header class="header">header</header>
<main class="main">main</main>
<footer class="footer">footer</footer>
</div>
</template>
2
3
4
5
6
7
8
但有的时候,使用模板文件并不是很方便,可以参考官方的案例 (opens new window)。对比来看,这种情况下,使用render函数来编写要更简洁一些。
# 使用render函数来开发组件
什么时候需要使用render函数呢?
简单来说:当你发现使用
.vue模板文件来开发一个组件特别麻烦,或者代码太过于冗余,这个时候就可以考虑使用render函数。
我们先从一个简单的案例出发:
如何将下面的一个**.vue**文件改造为一个用render函数开发的组件。在改造前,建议先看下render函数的基本用法 (opens new window),以及深入数据对象 (opens new window)这2个章节。
<template>
<div class="my-modal">
<header class="header">header</header>
<main class="main">content</main>
<footer class="footer">footer</footer>
</div>
</template>
2
3
4
5
6
7
使用render函数进行改造(基于h函数):
export default {
props: {},
render(h) {
let vnode = h('div',{ class: 'my-modal' },[
h('header',{ class: 'header' }, ['header']),
h('main',{ class: 'main' }, ['content']),
h('footer',{ class: 'footer' }, ['footer']),
]);
return vnode;
}
}
2
3
4
5
6
7
8
9
10
11
我们发现,每一个html标签,我们都需要使用h函数来创建对应的vnode。同时,vnode的children,也需要使用h函数来创建。当层级不多时,这种写法还勉强能接受。
如果层级多,结构复杂,这种时候,这种手写的方式简直无法忍受。为了解决这个问题,Vue官方推出了一个jsx插件 (opens new window)来简化我们频繁使用h函数的方式。官方也给出了对应的demo,参考vue-jsx的使用 (opens new window)。
根据文档,我们将上面的代码进行改造(基于jsx):
export default {
props: {},
render(h) {
let vnode = (
<div class='my-modal'>
<div class="header">header</div>
<div class="main">content</div>
<div class="footer">footer</div>
</div>
);
return vnode;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
这时我们发现,使用jsx来编写,既能保证js的灵活,又保证了同vue模板文件一样清晰的结构。
# 使用jsx开发组件
在我们日常的业务开发中,开发自定义组件时,为了保证组件的灵活性,通常我们会使用slot插槽。接下来,我们将通过一个稍微复杂的案例,来展示如何使用jsx (opens new window)来编写组件,同时学习如何在jsx中使用插槽 (opens new window)。
假设我们需要实现一个Modal组件,包括header、content、footer三个部分。每个部分,用户都可以自定义。使用.vue,组件实现如下:
//modal.vue
<template>
<div class="my-modal">
<p>{{ title }} </p>
<!-- 具名插槽 -->
<header class="header">
<slot name="header">
<p>header</p>
</slot>
</header>
<main class="main">
<!-- 默认插槽 -->
<slot>
<div class="wrapper">content </div>
</slot>
</main>
<footer class="footer">
<!-- 作用域插槽 -->
<slot name="footer" v-bind:user="infos">
<p @click="handler">footer:{{ infos }}</p>
</slot>
</footer>
</div>
</template>
<script>
export default {
name: 'modal',
inheritAttrs: false,
props: {
infos: {
type: Object,
required: true
},
title: {
type: String
}
},
methods: {
handler(){
this.$emit('footer-click',this.infos)
}
}
}
</script>
<style>
.my-modal {
border: 1px solid #c9c8c8;
padding: 5px;
margin: 2px 0;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
我们使用jsx来改造:
在使用jsx之前,你需要了解$slot (opens new window)与$scopedSlots (opens new window)的区别。官方文档中已注明,所有的$slots现在都会作为函数暴露在$scopedSlots中,因此为了兼容Vue3的代码,推荐使用$scopedSlots来访问插槽内容。
// modal.js
export default {
name: 'modal',
inheritAttrs: false,
props: {
infos: {
type: Object,
required: true,
},
title: {
type: String
}
},
methods: {
handler() {
this.$emit('footer-click', this.infos)
}
},
render(h) {
let { infos, title, $scopedSlots } = this;
// header slot,处理当slot为header的插槽不存在时,使用默认的vnode
let headerVnode = $scopedSlots.header ?
$scopedSlots.header() : (<p>header</p>);
// default slot,,处理当默认插槽不存在时,使用默认的vnode
let mainVnode = $scopedSlots.default ?
$scopedSlots.default() : (<div class="wrapper"> content </div>);
let _attrs = {
on: {
click: this.handler
}
}
// scopedSlots and params,处理作用域插槽并传递参数
let footerVnode = $scopedSlots.footer ?
$scopedSlots.footer({
user: infos
}) : (<p {..._attrs }>footer:{ JSON.stringify(infos) }</p>);
let vnode = (
<div class="my-modal">
<p>{ title } </p>
<header class='header'> { headerVnode } </header>
<main class='main'>{ mainVnode }</main>
<footer class='footer'>{footerVnode}</footer>
</div>
);
return vnode;
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
最终我们在App.vue中使用这个改造后的modal.jsx组件,并对比modal.vue组件:
// App.vue
<template>
<div id="app">
<modal-vue :infos="infos" title="vue组件" @footer-click="clickHandle">
<h4 slot="header">header</h4>
<p slot="default">自定义默认插槽.</p>
<template v-slot:footer="slotProps">
<p>自定义作用域插槽:</p>
<span>name: {{ slotProps.user.name }}</span>
<span>age: {{ slotProps.user.age }}</span>
</template>
</modal-vue>
<modal-jsx :infos="infos" title="jsx组件" @footer-click="clickHandle">
<template v-slot:header>
<h4>header</h4>
</template>
<template v-slot:default>
<p>自定义默认插槽.</p>
</template>
<template v-slot:footer="slotProps">
<p>自定义作用域插槽:</p>
<span>name: {{ slotProps.user.name }}</span>
<span>age: {{ slotProps.user.age }}</span>
</template>
</modal-jsx>
</div>
</template>
<script>
import ModalVue from "./components/modal.vue";
import ModalJsx from './components/modal-jsx.js';
export default {
name: "App",
data() {
return {
infos: {
name: "zhangsan",
age: 100,
},
};
},
components: {
ModalVue,
ModalJsx
},
methods: {
clickHandle(val) {
console.log("foot-click", val);
},
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

两种方式创建的modal组件