函数式组件比较特殊,也非常的灵活,它可以根据传入该组件的内容动态的渲染成任意想要的节点,在一些比较复杂的高级组件里用到,比如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 源码分析(三十) 高级应用 函数式组件 详解的更多相关文章
-
Vue.js 源码分析(三十一) 高级应用 keep-alive 组件 详解
当使用is特性切换不同的组件时,每次都会重新生成组件Vue实例并生成对应的VNode进行渲染,这样是比较花费性能的,而且切换重新显示时数据又会初始化,例如: <!DOCTYPE html> ...
-
Vue.js 源码分析(二十七) 高级应用 异步组件 详解
当我们的项目足够大,使用的组件就会很多,此时如果一次性加载所有的组件是比较花费时间的.一开始就把所有的组件都加载是没必要的一笔开销,此时可以用异步组件来优化一下. 异步组件简单的说就是只有等到在页面里 ...
-
Vue.js 源码分析(二十) 指令篇 v-once指令详解
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值,例如:<p>Message: {{ msg }}</p>以后每当msg属性发生了改变,插值处的内 ...
-
Vue.js 源码分析(三十二) 总结
第一次写博客,坚持了一个多月时间,Vue源码分析基本分析完了,回过头也看也漏了一些地方,比如双向绑定里的观察者模式,也可以说是订阅者模式,也就是Vue里的Dep.Watcher等这些函数的作用,网上搜 ...
-
Vue.js 源码分析(二十三) 指令篇 v-show指令详解
v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如: <!DOCTYPE html> < ...
-
Vue.js 源码分析(二十一) 指令篇 v-pre指令详解
该指令会跳过所在元素和它的子元素的编译过程,也就是把这个节点及其子节点当作一个静态节点来处理,例如: <!DOCTYPE html> <html lang="en" ...
-
Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持).过滤器应该被添加在 JavaScrip ...
-
Vue.js 源码分析(五) 基础篇 方法 methods属性详解
methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...
-
Vue.js 源码分析(二十八) 高级应用 transition组件 详解
transition组件可以给任何元素和组件添加进入/离开过渡,但只能给单个组件实行过渡效果(多个元素可以用transition-group组件,下一节再讲),调用该内置组件时,可以传入如下特性: n ...
随机推荐
-
unsafe clr sql 部署向导
1.部署安全权限的简单的程序集,非常简单,只需要正常发布就行,基本可以成功,前提是账号有部署clr assembly的权限 第一步: 创建登陆账号,尽量不要使用sa吧,默认架构为:dbo即可 第二部: ...
-
如何在Actionbarsherlock中一直显示overflow效果?
对Android开发一致性有一定考虑的程序员应当或多或少对Actionbarsherlock这个库有一定的了解.Actionbarsherlock的产生是因为Android在3.0(API 11)之后 ...
-
从Evernote迁移到Wiz
实在受不了evernote没完没了的弹出广告和让升级账号,我不过就是想安安静静的记个笔记,真不想看您的广告好吧.于是无奈这下,只能选择转换到别的笔记工具阵营. 由于以前一直听说OneNote是仅次于E ...
-
通过ReentrantLock源代码分析AbstractQueuedSynchronizer独占模式
1. 重入锁的概念与作用 reentrant 锁意味着什么呢?简单来说,它有一个与获取锁相关的计数器,如果已占有锁的某个线程再次获取锁,那么lock方法中将计数器就加1后就会立刻返回.当释 ...
-
WinForm打包后皮肤无效(解决方案)
今天在项目中用到了SkinEngine,遇到了一点问题,总结出点心得: 问题: 为什么我们在开发中皮肤还是显示的,但是打包后就没有效果了? 我也遇到了同样的问题,一开始以为是路径的问题: 我不知道大家 ...
-
CentOS中基于不同版本安装重复包的解决方案
http://blog.chinaunix.net/uid-21710705-id-3039675.html
-
grep in linux
1.作用linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.grep全称是Global Regular Expression Print,表示全局正 ...
-
C/C++语言参数传递----函数/方法 参数的指针引用传递
int m_value = 1; void func(int *p) { p = &m_value; } int main(int argc, char *argv[]) { int n = ...
-
C#- 泛型去除重复项
今天被这个问题纠结了好一会.如何去除重复项,我遇到的问题是,在判断是否重复的条件是有两个,一个信息来源,一个是信息标题. 最后使用了哈希后很好的解决,感觉挺高效的.代码贴下,做一个备忘 //防止群发, ...
-
ubuntu apt-get常用命令的使用
packagename指代为软件包的名称 apt-get install packagename 安装一个新软件包(参见下文的aptitude) apt-get remove ...