zoukankan      html  css  js  c++  java
  • 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)
        }))
    },

    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

  • 相关阅读:
    python数据类型以及模块的含义
    python基础语言以及if/while语句结构
    subprocess模块
    linux 管道通信socket 全双工示例
    整体框架
    licode_WebrtcConnection
    webrtc杂谈(转)
    修改背景颜色
    激活NX窗口的按钮
    NX屏蔽窗口的按钮
  • 原文地址:https://www.cnblogs.com/greatdesert/p/11277686.html
Copyright © 2011-2022 走看看