Vue.js 源码分析(三十) 高级应用 函数式组件 详解

时间:2022-09-26 18:11:08

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

输出如下:

Vue.js 源码分析(三十) 高级应用 函数式组件 详解

对应的DOM树如下:

Vue.js 源码分析(三十) 高级应用 函数式组件 详解

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

Vue.js 源码分析(三十) 高级应用 函数式组件 详解

这是在因为我们再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如下:

Vue.js 源码分析(三十) 高级应用 函数式组件 详解

也就是获取到了组件上传入的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返回的对象如下:

Vue.js 源码分析(三十) 高级应用 函数式组件 详解

回到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&period;js 源码分析&lpar;三十一&rpar; 高级应用 keep-alive 组件 详解

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

  2. Vue&period;js 源码分析&lpar;二十七&rpar; 高级应用 异步组件 详解

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

  3. Vue&period;js 源码分析&lpar;二十&rpar; 指令篇 v-once指令详解

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

  4. Vue&period;js 源码分析&lpar;三十二&rpar; 总结

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

  5. Vue&period;js 源码分析&lpar;二十三&rpar; 指令篇 v-show指令详解

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

  6. Vue&period;js 源码分析&lpar;二十一&rpar; 指令篇 v-pre指令详解

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

  7. Vue&period;js 源码分析&lpar;十一&rpar; 基础篇 过滤器 filters属性详解

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

  8. Vue&period;js 源码分析&lpar;五&rpar; 基础篇 方法 methods属性详解

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

  9. Vue&period;js 源码分析&lpar;二十八&rpar; 高级应用 transition组件 详解

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

随机推荐

  1. unsafe clr sql 部署向导

    1.部署安全权限的简单的程序集,非常简单,只需要正常发布就行,基本可以成功,前提是账号有部署clr assembly的权限 第一步: 创建登陆账号,尽量不要使用sa吧,默认架构为:dbo即可 第二部: ...

  2. 如何在Actionbarsherlock中一直显示overflow效果?

    对Android开发一致性有一定考虑的程序员应当或多或少对Actionbarsherlock这个库有一定的了解.Actionbarsherlock的产生是因为Android在3.0(API 11)之后 ...

  3. 从Evernote迁移到Wiz

    实在受不了evernote没完没了的弹出广告和让升级账号,我不过就是想安安静静的记个笔记,真不想看您的广告好吧.于是无奈这下,只能选择转换到别的笔记工具阵营. 由于以前一直听说OneNote是仅次于E ...

  4. 通过ReentrantLock源代码分析AbstractQueuedSynchronizer独占模式

    1. 重入锁的概念与作用       reentrant 锁意味着什么呢?简单来说,它有一个与获取锁相关的计数器,如果已占有锁的某个线程再次获取锁,那么lock方法中将计数器就加1后就会立刻返回.当释 ...

  5. WinForm打包后皮肤无效(解决方案)

    今天在项目中用到了SkinEngine,遇到了一点问题,总结出点心得: 问题: 为什么我们在开发中皮肤还是显示的,但是打包后就没有效果了? 我也遇到了同样的问题,一开始以为是路径的问题: 我不知道大家 ...

  6. CentOS中基于不同版本安装重复包的解决方案

    http://blog.chinaunix.net/uid-21710705-id-3039675.html

  7. grep in linux

    1.作用linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.grep全称是Global Regular Expression Print,表示全局正 ...

  8. C&sol;C&plus;&plus;语言参数传递----函数&sol;方法 参数的指针引用传递

    int m_value = 1; void func(int *p) { p = &m_value; } int main(int argc, char *argv[]) { int n = ...

  9. C&num;- 泛型去除重复项

    今天被这个问题纠结了好一会.如何去除重复项,我遇到的问题是,在判断是否重复的条件是有两个,一个信息来源,一个是信息标题. 最后使用了哈希后很好的解决,感觉挺高效的.代码贴下,做一个备忘 //防止群发, ...

  10. ubuntu apt-get常用命令的使用

             packagename指代为软件包的名称 apt-get install packagename     安装一个新软件包(参见下文的aptitude) apt-get remove ...