经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » HTML5 » 查看文章
Vue.js 源码分析(三十) 高级应用 函数式组件 详解
来源:cnblogs  作者:大沙漠  时间:2019/8/2 8:48:20  对本文有异议

函数式组件比较特殊,也非常的灵活,它可以根据传入该组件的内容动态的渲染成任意想要的节点,在一些比较复杂的高级组件里用到,比如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 选项,则该对象包含了应当被注入的属性。

例如:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <smart-list :items=items></smart-list>
  11. </div>
  12. <script>
  13. Vue.config.productionTip=false;
  14. Vue.config.devtools=false;
  15. Vue.component('smart-list', {
  16. functional: true,                       //指定这是一个函数式组件
  17. render: function (createElement, context) {
  18. function appropriateListComponent (){
  19. if (context.props.items.length==0){ //当父组件传来的items元素为空时渲染这个
  20. return {template:"<div>Enpty item</div>"}
  21. }
  22. return 'ul'
  23. }
  24. return createElement(appropriateListComponent(),Array.apply(null,{length:context.props.items.length}).map(function(val,index){
  25. return createElement('li',context.props.items[index].name)
  26. }))
  27. },
  28. props: {
  29. items: {type: Array,required: true},
  30. isOrdered: Boolean
  31. }
  32. });
  33. var app = new Vue({
  34. el: '#app',
  35. data:{
  36. items:[{name:'a',id:0},{name:'b',id:1},{name:'c',id:2}]
  37. }
  38. })
  39. </script>
  40. </body>
  41. </html>

输出如下:

对应的DOM树如下:

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

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

 

源码分析


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

  1. function createComponent ( //第4181行 创建组件节点
  2. Ctor,
  3. data,
  4. context,
  5. children,
  6. tag
  7. ) {
  8. /**/
  9. var propsData = extractPropsFromVNodeData(data, Ctor, tag); //对props做处理
  10. // functional component
  11. if (isTrue(Ctor.options.functional)) { //如果options.functional为true,即这是对函数组件
  12. return createFunctionalComponent(Ctor, propsData, data, context, children) //则调用createFunctionalComponent()创建函数式组件
  13. }
  14. /**/

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

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

  1. function createFunctionalComponent ( //第4026行 函数式组件的实现
  2. Ctor, //Ctro:组件的构造对象(Vue.extend()里的那个Sub函数)
  3. propsData, //propsData:父组件传递过来的数据(还未验证)
  4. data, //data:组件的数据
  5. contextVm, //contextVm:Vue实例
  6. children //children:引用该组件时定义的子节点
  7. ) {
  8. var options = Ctor.options;
  9. var props = {};
  10. var propOptions = options.props;
  11. if (isDef(propOptions)) { //如果propOptions非空(父组件向当前组件传入了信息)
  12. for (var key in propOptions) { //遍历propOptions
  13. props[key] = validateProp(key, propOptions, propsData || emptyObject); //调用validateProp()依次进行检验
  14. }
  15. } else {
  16. if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
  17. if (isDef(data.props)) { mergeProps(props, data.props); }
  18. }
  19. var renderContext = new FunctionalRenderContext( //创建一个函数的上下文
  20. data,
  21. props,
  22. children,
  23. contextVm,
  24. Ctor
  25. );
  26. var vnode = options.render.call(null, renderContext._c, renderContext); //执行render函数,参数1为createElement,参数2为renderContext,也就是我们在组件内定义的render函数
  27.  
  28. if (vnode instanceof VNode) {
  29. return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options)
  30. } else if (Array.isArray(vnode)) {
  31. var vnodes = normalizeChildren(vnode) || [];
  32. var res = new Array(vnodes.length);
  33. for (var i = 0; i < vnodes.length; i++) {
  34. res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options);
  35. }
  36. return res
  37. }
  38. }

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

  1. function FunctionalRenderContext ( //第3976行 创建rendrer函数的上下文 parent:调用当前组件的父组件实例
  2. data,
  3. props,
  4. children,
  5. parent,
  6. Ctor
  7. ) {
  8. var options = Ctor.options;
  9. // ensure the createElement function in functional components
  10. // gets a unique context - this is necessary for correct named slot check
  11. var contextVm;
  12. if (hasOwn(parent, '_uid')) { //如果父Vue含有_uid属性(是个Vue实例)
  13. contextVm = Object.create(parent); //以parent为原型,创建一个实例,保存到contextVm里面
  14. // $flow-disable-line
  15. contextVm._original = parent;
  16. } else {
  17. // the context vm passed in is a functional context as well.
  18. // in this case we want to make sure we are able to get a hold to the
  19. // real context instance.
  20. contextVm = parent;
  21. // $flow-disable-line
  22. parent = parent._original;
  23. }
  24. var isCompiled = isTrue(options._compiled);
  25. var needNormalization = !isCompiled;
  26. this.data = data; //data
  27. this.props = props; //props
  28. this.children = children; //children
  29. this.parent = parent; //parent,也就是引用当前函数组件的Vue实例
  30. this.listeners = data.on || emptyObject; //自定义事件
  31. this.injections = resolveInject(options.inject, parent);
  32. this.slots = function () { return resolveSlots(children, parent); };
  33. // support for compiled functional template
  34. if (isCompiled) {
  35. // exposing $options for renderStatic()
  36. this.$options = options;
  37. // pre-resolve slots for renderSlot()
  38. this.$slots = this.slots();
  39. this.$scopedSlots = data.scopedSlots || emptyObject;
  40. }
  41. if (options._scopeId) {
  42. this._c = function (a, b, c, d) {
  43. var vnode = createElement(contextVm, a, b, c, d, needNormalization);
  44. if (vnode && !Array.isArray(vnode)) {
  45. vnode.fnScopeId = options._scopeId;
  46. vnode.fnContext = parent;
  47. }
  48. return vnode
  49. };
  50. } else {
  51. this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); }; //初始化一个_c函数,等于全局的createElement函数
  52. }
  53. }

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

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

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

在我们自定义的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

原文链接:http://www.cnblogs.com/greatdesert/p/11277686.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号