函数式组件比较特殊,也非常的灵活,它可以根据传入该组件的内容动态的渲染成任意想要的节点,在一些比较复杂的高级组件里用到,比如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. Python爬虫实践~BeautifulSoup+urllib+Flask实现静态网页的爬取

    爬取的网站类型: 论坛类网站类型 涉及主要的第三方模块: BeautifulSoup:解析.遍历页面 urllib:处理URL请求 Flask:简易的WEB框架 介绍: 本次主要使用urllib获取网 ...

  2. 【LOJ#6682】梦中的数论(min_25筛)

    [LOJ#6682]梦中的数论(min_25筛) 题面 LOJ 题解 注意题意是\(j|i\)并且\((j+k)|i\), 不难发现\(j\)和\((j+k)\)可以任意取\(i\)的任意因数,且\( ...

  3. Koa 中间件的执行

    Node.js 中请求的处理 讨论 Koa 中间件前,先看原生 Node.js 中是如何创建 server 和处理请求的. node_server.js const http = require(&q ...

  4. .net core程序强制以管理员权限启动

    当我们编写windows程序的时候,很多时候需要程序默认以管理员权限运行,以前在.net 程序中直接新建一个app.manifest,设置requestedExecutionLevel 节点即可 &l ...

  5. 5.智能快递柜(通信篇-Server程序)

    1.智能快递柜(开篇) 2.智能快递柜(终端篇) 3.智能快递柜(通信篇-HTTP) 4.智能快递柜(通信篇-SOCKET) 5.智能快递柜(通信篇-Server程序) 6.智能快递柜(平台篇) 7. ...

  6. Web前端基础(2):HTML(二)

    1. body中的相关标签 1.1 标题标签:hn HTML提供<hn>系列标签,用于修饰标签,包含:<h1>.<h2>.<h3>.<h4> ...

  7. 重新安装和更新所有的 nuget包

    重新安装指定项目中所有的 nuget 包 Update-Package -ProjectName MyProject –reinstall 更新指定项目中所有的 nuget 包 Update-Pack ...

  8. JavaScript获取Django模板中指定键值的数据,使用过滤器

    Django中利用js来操作数据的常规操作一般为点(.)操作符来获取字典或列表的数据,一般如{{data.0}},{{data.arg}} 但有时如果数据是嵌套类型的数据时,直接获取某个值就变得困难了 ...

  9. MySQL第三课

    首先创建一个数据库: CREATE DATABASE ku; Query OK, 1 row affected 查看一下是否有此数据库: SHOW DATABASES; +-------------- ...

  10. Python创建virtualenv虚拟环境方法

    一.通过virtualenv软件创建 安装:        -pip3 install virtualenv    创建虚拟环境:        -(1)virtualenv wannings-ms- ...