函数式组件原理

前一篇文章分析了函数式组件用法(https://blog.csdn.net/liubangbo/article/details/104034122) ,这篇文章从源码的角度看看函数式组件,看看它怎么是无状态的,以及怎么没有实例的。我们就用之前文章的例子来进行分析。

要渲染的组件如下:

    <div id="app">
        <list-view-comp :id="'listViewId'" :list-data="listData"></list-view-comp>
    </div>

list-view-comp 就是函数式组件(在options里面定义了functional属性为true),id和list-data是传递给这个组件的props数据。经过模板编译之后,走到创建组件这个地方,我们就从创建组件开始分析:

  function _createElement (
    context,
    tag,
    data,
    children,
    normalizationType
  ) {
    // some code 

      if (config.isReservedTag(tag)) {
        // platform built-in elements
        vnode = new VNode(
          config.parsePlatformTagName(tag), data, children,
          undefined, undefined, context
        );
      } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 
                  'components', tag))) {
        // component
        console.log("liubbc comp tag: " + tag)
        console.log("liubbc comp  data: " + JSON.stringify(data))
        vnode = createComponent(Ctor, data, context, children, tag);
      } else {
        // unknown or unlisted namespaced elements
        // check at runtime because it may get assigned a namespace when its
        // parent normalizes children
        vnode = new VNode(
          tag, data, children,
          undefined, undefined, context
        );
      }

传递给createComponent函数的参数tag,data  打印如下:

liubbc comp tag: list-view-comp
liubbc comp  data: {"attrs":{"id":"listViewId","list-data":[{"icon":"./icon.png","logo":"./logo.png","title":"title 1","subTitle":"sub title 1"}]}}

data参数是个object对象,attrs属性包含传递给这个组件的 id, list-data数据。接着我们看createComponent函数。

  function createComponent (
    Ctor,
    data,
    context,
    children,
    tag
  ) {
    //some code

    // extract props
    var propsData = extractPropsFromVNodeData(data, Ctor, tag);

    console.log("liubbc createComponent data is: " + JSON.stringify(data));
    console.log("liubbc createComponent propsData is: " + JSON.stringify(propsData));
    // functional component
    if (isTrue(Ctor.options.functional)) {
      return createFunctionalComponent(Ctor, propsData, data, context, children)
    }

    //some code

  }

 加的两行打印如下:

liubbc createComponent data is: {"attrs":{"id":"listViewId"}}
liubbc createComponent propsData is: {"listData":[{"icon":"./icon.png","logo":"./logo.png","title":"title 1","subTitle":"sub title 1"}]}

createComponent函数对data数据进行了处理,把父组件的动态数据提取出来传递给了子组件,完成了父组件向子组件传递数据的过程(props down)。data数据中的attrs对象中只剩下id属性了。

接下来会判断组件的选项里面是否定义了functional,如果为true,则createFunctionalComponent,并直接返回。主角粉墨登场了,我们就一探createFunctionalComponent究竟。

  function createFunctionalComponent (
    Ctor,
    propsData,
    data,
    contextVm,
    children
  ) {
    var options = Ctor.options;
    var props = {};
    var propOptions = options.props;
    if (isDef(propOptions)) { //如果在函数式组件的选项中定义了props选项
      for (var key in propOptions) {
        props[key] = validateProp(key, propOptions, propsData || emptyObject);
      }
    } else {
      //没有在函数式组件的选项中定义props选项,则用data中的attrs和props数据
      if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
      if (isDef(data.props)) { mergeProps(props, data.props); }
    }

    //new了一个渲染上下文实例,这个实例里面有data,props等属性,以及_c方法(createElement方法)
    var renderContext = new FunctionalRenderContext(
      data,
      props,
      children,
      contextVm,
      Ctor
    );

    //用call方法,把函数式组件中的render函数中的this设置为null, 这样render函数就没有实例了。并
    //传递了_c方法,以及新new的渲染上下文实例
    var vnode = options.render.call(null, renderContext._c, renderContext);

    //最后生成了vnode
    if (vnode instanceof VNode) {
      return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
    } 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, renderContext);
      }
      return res
    }
  }

我们对关键代码进行了注释。最后我们看看FunctionalRenderContext 构造函数:

  function FunctionalRenderContext (
    data,
    props,
    children,
    parent,
    Ctor
  ) {
    var this$1 = this;

    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')) {
      contextVm = Object.create(parent);
      // $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;
    this.parent = parent;
    this.listeners = data.on || emptyObject;
    this.injections = resolveInject(options.inject, parent);
    this.slots = function () {
      if (!this$1.$slots) {
        normalizeScopedSlots(
          data.scopedSlots,
          this$1.$slots = resolveSlots(children, parent)
        );
      }
      return this$1.$slots
    };

    Object.defineProperty(this, 'scopedSlots', ({
      enumerable: true,
      get: function get () {
        return normalizeScopedSlots(data.scopedSlots, this.slots())
      }
    }));

    // 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 = normalizeScopedSlots(data.scopedSlots, this.$slots);
    }

    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 {
      //其实_c函数就是createElement函数,用来创建vnode的
      this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); };
    }
  }

哪里有错误,或不妥之处,欢迎指正。谢谢!如果感觉写的还可以,对您有帮助也欢迎点赞啊。

发布了180 篇原创文章 · 获赞 16 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/liubangbo/article/details/104073269
今日推荐