函数式组件比较特殊,也非常的灵活,它可以根据传入该组件的内容动态的渲染成任意想要的节点,在一些比较复杂的高级组件里用到,比如Vue-router里的<router-view>组件就是一个函数式组件。

因为函数式组件只是函数,所以渲染开销也低很多,当需要做这些时,函数式组件非常有用:

  程序化地在多个组件中选择一个来代为渲染。

  在将children、props、data传递给子组件之前操作它们。

函数式组件的定义和普通组件类似,也是一个对象,不过而且为了区分普通的组件,定义函数式组件需要指定一个属性,名为functional,值为true,另外需要自定义一个render函数,该render函数可以带两个参数,分别如下:

createElement                  等于全局的createElement函数,用于创建VNode

context                             一个对象,组件需要的一切都是通过context参数传递

context对象可以包含如下属性:

parent        ;父组件的引用
        props        ;提供所有prop的对象,经过验证了
        children    ;VNode 子节点的数组
        slots        ;一个函数,返回了包含所有插槽的对象
        scopedSlots    ;个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
        data        ;传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
        listeners    ;组件的自定义事件
        injections     ;如果使用了 inject 选项,则该对象包含了应当被注入的属性。

例如:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
</head>
<body>
<div id="app">
<smart-list :items=items></smart-list>
</div>
<script>
Vue.config.productionTip=false;
Vue.config.devtools=false;
Vue.component('smart-list', {
functional: true,                       //指定这是一个函数式组件
render: function (createElement, context) {
function appropriateListComponent (){
if (context.props.items.length==0){ //当父组件传来的items元素为空时渲染这个
return {template:"<div>Enpty item</div>"}
}
return 'ul'
}
return createElement(appropriateListComponent(),Array.apply(null,{length:context.props.items.length}).map(function(val,index){
return createElement('li',context.props.items[index].name)
}))
},
props: {
items: {type: Array,required: true},
isOrdered: Boolean
}
});
var app = new Vue({
el: '#app',
data:{
items:[{name:'a',id:0},{name:'b',id:1},{name:'c',id:2}]
}
})
</script>
</body>
</html>

输出如下:

对应的DOM树如下:

如果items.item为空数组,则会渲染成:

这是在因为我们再render内做了判断,返回了该值

源码分析


组件在Vue实例化时会先执行createComponent()函数,在该函数内执行extractPropsFromVNodeData(data, Ctor, tag)从组件的基础构造器上获取到props信息后就会判断options.functional是否为true,如果为true则执行createFunctionalComponent函数,如下:

  function createComponent (  //第4181行 创建组件节点
Ctor,
data,
context,
children,
tag
) {
/**/
var propsData = extractPropsFromVNodeData(data, Ctor, tag); //对props做处理 // functional component
if (isTrue(Ctor.options.functional)) { //如果options.functional为true,即这是对函数组件
return createFunctionalComponent(Ctor, propsData, data, context, children) //则调用createFunctionalComponent()创建函数式组件
}
/*略*/

例子执行到这里对应的propsData如下:

也就是获取到了组件上传入的props,然后执行createFunctionalComponent函数,并将结果返回,该函数如下:

function createFunctionalComponent (      //第4026行  函数式组件的实现
Ctor, //Ctro:组件的构造对象(Vue.extend()里的那个Sub函数)
propsData, //propsData:父组件传递过来的数据(还未验证)
data, //data:组件的数据
contextVm, //contextVm:Vue实例
children //children:引用该组件时定义的子节点
) {
var options = Ctor.options;
var props = {};
var propOptions = options.props;
if (isDef(propOptions)) { //如果propOptions非空(父组件向当前组件传入了信息)
for (var key in propOptions) { //遍历propOptions
props[key] = validateProp(key, propOptions, propsData || emptyObject); //调用validateProp()依次进行检验
}
} else {
if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
if (isDef(data.props)) { mergeProps(props, data.props); }
} var renderContext = new FunctionalRenderContext( //创建一个函数的上下文
data,
props,
children,
contextVm,
Ctor
); var vnode = options.render.call(null, renderContext._c, renderContext); //执行render函数,参数1为createElement,参数2为renderContext,也就是我们在组件内定义的render函数 if (vnode instanceof VNode) {
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options)
} else if (Array.isArray(vnode)) {
var vnodes = normalizeChildren(vnode) || [];
var res = new Array(vnodes.length);
for (var i = 0; i < vnodes.length; i++) {
res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options);
}
return res
}
}

FunctionalRenderContext就是一个函数对应,new的时候会给当前对象设置一些data、props之类的属性,如下:

function FunctionalRenderContext (      //第3976行 创建rendrer函数的上下文 parent:调用当前组件的父组件实例
data,
props,
children,
parent,
Ctor
) {
var options = Ctor.options;
// ensure the createElement function in functional components
// gets a unique context - this is necessary for correct named slot check
var contextVm;
if (hasOwn(parent, '_uid')) { //如果父Vue含有_uid属性(是个Vue实例)
contextVm = Object.create(parent); //以parent为原型,创建一个实例,保存到contextVm里面
// $flow-disable-line
contextVm._original = parent;
} else {
// the context vm passed in is a functional context as well.
// in this case we want to make sure we are able to get a hold to the
// real context instance.
contextVm = parent;
// $flow-disable-line
parent = parent._original;
}
var isCompiled = isTrue(options._compiled);
var needNormalization = !isCompiled; this.data = data; //data
this.props = props; //props
this.children = children; //children
this.parent = parent; //parent,也就是引用当前函数组件的Vue实例
this.listeners = data.on || emptyObject; //自定义事件
this.injections = resolveInject(options.inject, parent);
this.slots = function () { return resolveSlots(children, parent); }; // support for compiled functional template
if (isCompiled) {
// exposing $options for renderStatic()
this.$options = options;
// pre-resolve slots for renderSlot()
this.$slots = this.slots();
this.$scopedSlots = data.scopedSlots || emptyObject;
} if (options._scopeId) {
this._c = function (a, b, c, d) {
var vnode = createElement(contextVm, a, b, c, d, needNormalization);
if (vnode && !Array.isArray(vnode)) {
vnode.fnScopeId = options._scopeId;
vnode.fnContext = parent;
}
return vnode
};
} else {
this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); }; //初始化一个_c函数,等于全局的createElement函数
}
}

对于例子来说执行到这里FunctionalRenderContext返回的对象如下:

回到createFunctionalComponent最后会执行我们的render函数,也就是例子里我们自定义的smart-list组件的render函数,如下:

render: function (createElement, context) {
function appropriateListComponent (){
if (context.props.items.length==0){ //当父组件传来的items元素为空时渲染这个
return {template:"<div>Enpty item</div>"}
}
return 'ul'
}
return createElement(appropriateListComponent(),Array.apply(null,{length:context.props.items.length}).map(function(val,index){ //调用createElement也就是Vue全局的createElement函数
return createElement('li',context.props.items[index].name)
}))
},

writer by:大沙漠 QQ:22969969

在我们自定义的render函数内,会先执行appropriateListComponent()函数,该函数会判断当前组件是否有传入items特性,如果有则返回ul,这样createElement的参数1就是ul了,也就是穿件一个tag为ul的虚拟VNode,如果没有传入items则返回一个内容为Emptry item的div

createElement的参数2是一个数组,每个元素又是一个createElement的返回值,Array.apply(null,{length:context.props.items.length})可以根据一个数组的个数再创建一个数组,新数组每个元素的值为undefined

Vue.js 源码分析(三十) 高级应用 函数式组件 详解的更多相关文章

  1. Vue.js 源码分析(三十一) 高级应用 keep-alive 组件 详解

    当使用is特性切换不同的组件时,每次都会重新生成组件Vue实例并生成对应的VNode进行渲染,这样是比较花费性能的,而且切换重新显示时数据又会初始化,例如: <!DOCTYPE html> ...

  2. Vue.js 源码分析(二十七) 高级应用 异步组件 详解

    当我们的项目足够大,使用的组件就会很多,此时如果一次性加载所有的组件是比较花费时间的.一开始就把所有的组件都加载是没必要的一笔开销,此时可以用异步组件来优化一下. 异步组件简单的说就是只有等到在页面里 ...

  3. Vue.js 源码分析(二十) 指令篇 v-once指令详解

    数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值,例如:<p>Message: {{ msg }}</p>以后每当msg属性发生了改变,插值处的内 ...

  4. Vue.js 源码分析(三十二) 总结

    第一次写博客,坚持了一个多月时间,Vue源码分析基本分析完了,回过头也看也漏了一些地方,比如双向绑定里的观察者模式,也可以说是订阅者模式,也就是Vue里的Dep.Watcher等这些函数的作用,网上搜 ...

  5. Vue.js 源码分析(二十三) 指令篇 v-show指令详解

    v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如: <!DOCTYPE html> < ...

  6. Vue.js 源码分析(二十一) 指令篇 v-pre指令详解

    该指令会跳过所在元素和它的子元素的编译过程,也就是把这个节点及其子节点当作一个静态节点来处理,例如: <!DOCTYPE html> <html lang="en" ...

  7. Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解

    Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持).过滤器应该被添加在 JavaScrip ...

  8. Vue.js 源码分析(五) 基础篇 方法 methods属性详解

    methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...

  9. Vue.js 源码分析(二十八) 高级应用 transition组件 详解

    transition组件可以给任何元素和组件添加进入/离开过渡,但只能给单个组件实行过渡效果(多个元素可以用transition-group组件,下一节再讲),调用该内置组件时,可以传入如下特性: n ...

随机推荐

  1. 简单node服务器demo,麻雀虽小,五脏俱全

    //本服务器要实现的功能如下: //1.静态资源服务器(能读取静态资源) //2.能接收get请求,并能处理参数 //3.能接收post请求,并能处理参数 const http = require(' ...

  2. jQuery 源码分析(十一) 队列模块 Queue详解

    队列是常用的数据结构之一,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).特点是先进先出,最先插入的元素最先被删除. 在jQuery内部,队列模块为动画模块提供基 ...

  3. Web前端基础(1):HTML(一)

    1. HTML概述 1.1 什么是HTML HTML称为超文本标记语言,是一种标识性的语言.它包括一系列标签.通过这些标签可以将网络上的文档格式统一,使分散的Internet资源连接为一个逻辑整体.H ...

  4. PlayJava Day006

    今日所学: /* 2019.08.19开始学习,此为补档. */ 构造方法没有返回值(即return为空). this:实例(对象)的引用. JVM:①static方法区:存静态数据   ②栈区:引用 ...

  5. Python笔记:设计模式之代理模式

    代理通常就是一个介于寻求方和提供方之间的中介系统.其核心思想就是客户端(寻求方)没有直接和提供方(真实对象)打交道,而是通过代理对象来完成提供方提供的资源或操作. 代理其实就是封装实际服务对象的包装器 ...

  6. Java生鲜电商平台-商品分类表和商品类型表的区别与数据库设计

    Java生鲜电商平台-商品分类表和商品类型表的区别与数据库设计   二者服务的对象不一样 目的也是不一样的 商品分类是为商品服务的 用来管理商品 商品类型是为扩展属性服务的 用来管理属性 举例:[转] ...

  7. centOS服务器安装mongodb

    1.为服务器添加mongodb的包管理工具,这就相当于在windows中安装npm,以便能用npm安装各种依赖.添加了这个包管理工具,才能在后面对mongodb做一系列操作. touch /etc/y ...

  8. linux下 sort | uniq | wc | less 几个命令的基本用法

    sort -f :忽略大小写的差异,例如 A 与 a 视为编码相同: -b :忽略最前面的空格符部分: -M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法: -n :使用『纯数字』进行 ...

  9. 【hexo+github搭建myblog】bash: npm: command not found 问题,疑似解决!关键词:NPM全局安装路径

    情况:打算用hexo+github搭建个人博客 1. hexo搭建,参考博文如下,非常感谢: Hexo+Github博客搭建完全教程 hexo从零开始到搭建完整 问题: 在最基本的安装步骤 (参考链接 ...

  10. [视频教程] ubuntu系统下安装最新版的MySQL

    视频地址: https://www.bilibili.com/video/av69256331/ 官网文档https://dev.mysql.com/doc/mysql-apt-repo-quick- ...