Render渲染函数和JSX

时间:2023-01-21 14:58:32

1.Render函数:render是用来替换temlate的,需要更灵活的模板的写法的时候,用render。

官网API地址:https://cn.vuejs.org/v2/guide/render-function.html

通常写的h为createElement的缩写,createElement 会返回虚拟节点 (virtual node)”,也常简写它为“VNode,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。

  • 第一个参数为{String | Object | Function}是一个 HTML 标签名、组件选项对象,为必选项。
  • 第二个参数为 {Object},是一个与模板中属性对应的数据对象。可选。
  • 第三个参数为{String | Array},是子级虚拟节点 (VNodes),由 `createElement()` 构建而成,也可以使用字符串来生成“文本虚拟节点”。可选。

1.1一个最简单的render函数例子:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import Bus from "./lib/bus";
import $ from "jquery"; Vue.config.productionTip = false;
Vue.prototype.$bus = Bus; new Vue({
router,
store,
//render: h => h(App)
render(h) {
return h('div', {
attrs: {
id:"box"
},
style: {
color:"blue"
}
},"Caoqi"
);
},
}).$mount("#app");

Render渲染函数和JSX

1.2当第一个参数为组件时的写法如下,引入的组件为之前博客中讲过的Count-To组件(https://www.cnblogs.com/qicao/p/10805715.html)

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import Bus from "./lib/bus";
import $ from "jquery";
import CountTo from "_c/count-to"; Vue.config.productionTip = false;
Vue.prototype.$bus = Bus; new Vue({
router,
store,
render: function(h) {
return h(CountTo, {
/**
* class作为一个保留字必须用引号包裹
* 接受一个字符串、对象或字符串和对象组成的数组
*/
// 'class':'count-up wrapper',
//class: ["count-to", true ? "classA" : "classB"],
class: { "count-to": 1 === 1 },
// 组件 prop
props: { endVal: 200 },
// DOM 属性
domProps: {
//innerHTML: "baz"
},
/**
* 事件监听器在 `on` 属性内,
* 但不再支持如 `v-on:keyup.enter` 这样的修饰器。需要在处理函数中手动检查 keyCode。
*/
on: {
"on-animation-end": function(val) {
console.log("animation end");
}
},
/**
* 仅用于组件,用于监听原生事件,而不是组件内部使用`vm.$emit` 触发的事件。
*/
nativeOn: {
click: () => console.log("I am clicked!")
},
/*自定义指令*/
directives: [],
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: "name-of-slot"
});
}
}).$mount("#app");

显示效果:

Render渲染函数和JSX

1.3 创建子级虚拟节点:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import Bus from "./lib/bus";
import $ from "jquery";
import CountTo from "_c/count-to"; Vue.config.productionTip = false;
Vue.prototype.$bus = Bus; new Vue({
router,
store,
/**
* @param {String | Array} h
* 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,也可以使用字符串来生成“文本虚拟节点”。可选。
*/
//render:h=>h('div','123')
render:function(h) {
return h('div', [
h('span','span1'),
h('span','span2'),
])
}
}).$mount("#app");

1.4使用 JavaScript 代替模板功能

模板代码:

<template>
<div>
<ul @click="handlerClick">
<li
@click.stop="handlerClick"
v-for="(item,index) in list"
:key="`list_item_${index}`"
>{{item.name}}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{
name: "张三"
},
{
name: "李四"
}
]
};
},
methods: {
handlerClick: function(event) {
console.log(event);
}
}
};
</script>

main.js:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import Bus from "./lib/bus";
import $ from "jquery";
import CountTo from "_c/count-to"; Vue.config.productionTip = false;
Vue.prototype.$bus = Bus; new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");

Render渲染函数和JSX

若使用render函数中的js代替模板:只要在原生的 JavaScript 中可以轻松完成的操作,Vue 的渲染函数就不会提供专有的替代方法。比如,在模板中使用的 v-if 和 v-for需要用JavaScript 的 if/else 和 map 来重写:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import Bus from "./lib/bus";
import $ from "jquery";
import CountTo from "_c/count-to"; Vue.config.productionTip = false;
Vue.prototype.$bus = Bus; const handleClick = event => {
console.log(event);
event.stopPropagation();
}; let list = [{ name: "张三" }, { name: "李四" }];
/**
* Array map用法:
* 功能:将原数组映射成新数组
* https://www.zhangxinxu.com/wordpress/2013/04/es5%E6%96%B0%E5%A2%9E%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95/#map
*/
const getLiEleArr = h => {
return list.map((item,index) =>
h(
"li",
{
on: {
click: handleClick
},
key:`list_item_${index}`
},
item.name
)
);
};
/**等效于 */
/*function getLiEleArr(h) {
return list.map(function(item, index) {
return h(
"li",
{
on: {
click: handleClick
},
key: `list_item_${index}`
},
item.name
);
});
}*/ new Vue({
router,
store,
render: function(h) {
return h(
"ul",
{
on: {
click: handleClick
}
},
getLiEleArr(h)
);
}
}).$mount("#app");

2.函数式组件:函数组件可以用render方式,可以用模板方式。函数组件主要用来做组件的外壳,也就是写模板之前,可以先对传进来的上下文做一些处理。这个“壳”的作用有点类似模板语法里的<template>标签,本身不会渲染,只是做包裹。

官方API地址:https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6

我们可以把函数式组件想像成组件里的一个函数,输入参数是渲染上下文(render context),返回值是渲染好的HTML

对于函数式组件,可以这样定义:

  • Stateless(无状态):组件自身是没有状态的
  • Instanceless(无实例):组件自身没有实例,也就是没有this,事件只能由父组件传递

例子:

<div id="app">
<smart-list :items=items></smart-list>
</div>

app.vue:

   //当父组件传过来的是空items时
var EmptyList = {template: '<p>Empty list</p>'};
//当父组件传来的items元素为对象类型时
var TableList = 'ul'
// 当父组件定义了isOrdered变量且为true
var UnorderedList = 'ul' //定义组件
Vue.component('smart-list', {
//标记为函数式组件
functional: true, //render函数
render: function (createElement, context) {
// console.log(context)//若不理解可以打印出来context来看看里面都有些什么东西 //规定组件的渲染规则
function appropriateListComp() {
//获取父组件传来的数据
var items = context.props.items; //若空,则返回前面定义的emptylist
if (items.length === 0) return EmptyList;
//若为对象
if (typeof items[0] === 'object') return TableList;
//其他
return UnorderedList
} //生成模板
return createElement(
//模板标记为渲染规则函数返回值
appropriateListComp(),
//模板子元素,返回一个数组
Array.apply(null, {length: context.props.items.length}).map(function (value, index) {
return createElement('li',context.props.items[index].name)
})
)
},
props: {
items: {
type: Array,
required: true
},
isOrdered: Boolean
}
});
new Vue({
el: '#app',
data:{
items:[
{
name:'a',
id:0
},
{
name:'b',
id:1
},
{
name:'c',
id:2
}
]
}
})

最终浏览器渲染结果:

<div id="app">
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
</div>

再举一个例子,代码目录结构如下:

Render渲染函数和JSX

render-page:

<template>
<div>
<list :list="list" :render="renderFunc"></list>
</div>
</template>
<script>
import List from "_c/list";
export default {
data() {
return {
list: [
{
name: "张三"
},
{
name: "李四"
}
]
};
},
components: {
List
},
methods: {
renderFunc: function(h, name) {
return h("i", {
style: {
color: "pink"
}
},name);
}
}
};
</script>

router.js:

import Home from "@/views/Home.vue";

export default [
{
path: "/",
alias: "/home_page",
name: "home", //加上name属性 命名路由
component: Home,
props: route => ({
food: route.query.food
}),
beforeEnter: (to, from, next) => {
// if (from.name === "about") alert("这是从about来的");
// else alert("这不是从about来的");
next();
}
},
{
path:
'/render_page',
name: 'render_page',
component: () => import('@/views/render-page.vue'
)
}

{
path: "*",
component: () => import("@/views/error_404.vue")
}
];

list/index.js:

import List from "./list.vue";
export default List;

list/list.vue:

<template>
<ul>
<li v-for="(item,index) in list" :key="`item_${index}`">
<span v-if="!render">{{item.name}}</span>
<render-dom v-else :render-func="render" :name="item.name"></render-dom>
</li>
</ul>
</template>
<script>
import RenderDom from "_c/render-dom";
export default {
name: "List",
components: {
RenderDom
},
props: {
list: {
type: Array,
default: () => []
},
render: {
type: Function,
default: () => {}
}
}
};
</script>

render-dom.js:

export default {
functional: true,
// Props 是可选的
props: {
name: String,
renderFunc: Function
},
// 为了弥补缺少的实例,提供第二个参数作为上下文
render: (h, ctx) => {
return ctx.props.renderFunc(h, ctx.props.name);
}
};

运行效果:

Render渲染函数和JSX

JSX(可参考https://juejin.im/post/5affa64df265da0b93488fdd):

什么是JSX:

JSX就是Javascript和XML结合的一种格式。React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。

将刚才的例子中的render-page.vue中的render函数改为jsx语法(它可以让我们回到更接近于模板的语法上):

<template>
<div>
<list :list="list" :render="render"></list>
</div>
</template>
<script>
import List from "_c/list";
export default {
data() {
return {
list: [
{
name: "张三"
},
{
name: "李四"
}
]
};
},
components: {
List
},
methods: {
render: function(h, name) {
// return h("i", {
// style: {
// color: "pink"
// }
// },name);
return (
////绑定事件需要用on前缀
<i on-click={this.handlerClick} style={{color:'pink'}}>{name}</i>
);
},
handlerClick(event){
console.log(event);
}
}
};
</script>

在JSX中使用组件(使用和上一个例子同样的目录结构):

注意在render或者JSX中写的组件是不需要在components中注册的。

render-page.vue:

<template>
<div>
<list :list="list" :render="render"></list>
</div>
</template>
<script>
import List from "_c/list";
import CountTo from "_c/count-to";
export default {
data() {
return {
list: [
{
number: 100
},
{
number: 200
}
]
};
},
components: {
List
},
methods: {
render: function(h, number) {
return (
//绑定事件需要用on-开头 绑定原生事件用nativeOn-开头
<CountTo nativeOn-click={this.nativeHandlerOn} on-on-animation-end={this.handlerEnd} endVal={number} />
);
},
handlerClick(event) {
console.log(event);
},
handlerEnd() {
console.log("End!!!");
},
nativeHandlerOn(){
console.log("这是原生事件");
}
}
};
</script>

list.vue:

<template>
<ul>
<li v-for="(item,index) in list" :key="`item_${index}`">
<span v-if="!render">{{item.number}}</span>
<render-dom v-else :render-func="render" :number="item.number"></render-dom>
</li>
</ul>
</template>
<script>
import RenderDom from "_c/render-dom";
export default {
name: "List",
components: {
RenderDom
},
props: {
list: {
type: Array,
default: () => []
},
render: {
type: Function,
default: () => {}
}
}
};
</script>

render-dom.js:

export default {
functional: true,
// Props 是可选的
props: {
number: Number,
renderFunc: Function
},
// 为了弥补缺少的实例,提供第二个参数作为上下文
render: (h, ctx) => {
return ctx.props.renderFunc(h, ctx.props.number);
}
};

效果图:

Render渲染函数和JSX

作用域插槽

作用域插槽就是父组件在调用子组件的时候给子组件传了一个插槽,这个插槽为作用域插槽,该插槽必须放在template标签里面,同时声明从子组件接收的数据放在一个自定义属性内,并定义该数据的渲染方式。适合的场景是至少包含三级以上的组件层级,是一种优秀的组件化方案!

render-page.vue:

<template>
<div>
<list :list="list">
<count-to slot-scope="count" :endVal="count.number"></count-to>
</list>
</div>
</template>
<script>
import List from "_c/list";
import CountTo from "_c/count-to";
export default {
data() {
return {
list: [
{
number: 100
},
{
number: 200
}
]
};
},
components: {
List,
CountTo
},
methods: {
}
};
</script>

list.vue:

<template>
<ul>
<li v-for="(item,index) in list" :key="`item_${index}`">
<!-- <span v-if="!render">{{item.number}}</span>
<render-dom v-else :render-func="render" :number="item.number"></render-dom> -->
<slot :number="item.number"></slot>
</li>
</ul>
</template>
<script>
import RenderDom from "_c/render-dom";
export default {
name: "List",
components: {
RenderDom
},
props: {
list: {
type: Array,
default: () => []
},
render: {
type: Function,
default: () => {}
}
}
};
</script>