Vue.js 源码分析(三十) 高级应用 函数式组件 详解
函数式组件比较特殊,也非常的灵活,它可以根据传入该组件的内容动态的渲染成任意想要的节点,在一些比较复杂的高级组件里用到,比如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) })) },
在我们自定义的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 源码分析(二十四) 高级应用 自定义指令详解
-
Vue.js 源码分析(三十一) 高级应用 keep-alive 组件 详解
-
Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解
-
Vue.js 源码分析(二十九) 高级应用 transition-group组件 详解
-
Vue.js 源码分析(二十八) 高级应用 transition组件 详解
-
Vue.js 源码分析(二十七) 高级应用 异步组件 详解
-
Vue.js 源码分析(二十五) 高级应用 插槽 详解
-
Vue.js 源码分析(三十一) 高级应用 keep-alive 组件 详解
-
Vue.js 源码分析(二十四) 高级应用 自定义指令详解