Vue技术探究Vue.js专区

Vue源码分析(11)--实例分析component,props

2017-07-08  本文已影响693人  风之化身呀

前言

本文是vue2.x源码分析的第十一篇,主要看component,props,slot的处理过程!

实例代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue</title>
  <script src="./vue11.js" type="text/javascript" charset="utf-8" ></script>
</head>
<body>
  <div id="app">
      <child :name='message'>
        <!-- <span>span from parent</span> -->
      </child>
  </div>
  <script>
    debugger;
    var child=Vue.component('child',{
        template:'<div>{{name}}<slot></slot></div>',
        props:['name']
    })
    var vm=new Vue({
      el:'#app',
      name:'app',
      data:{
        message:'message from parent'
      },
    });
  </script>
</body>
</html>

1、三个全局API(Vue.component、Vue.directive、Vue.filter)

initAssetRegisters函数在initGlobalAPI时被调用

    function initAssetRegisters (Vue) {
      //config._assetTypes=['component','directive','filter']
      config._assetTypes.forEach(function (type) {
        Vue[type] = function (id,definition) {
          if (!definition) {
            return this.options[type + 's'][id]
          } else {
            {
              if (type === 'component' && config.isReservedTag(id)) {
                warn(
                  'Do not use built-in or reserved HTML elements as component ' +
                  'id: ' + id
                );
              }
            }
            if (type === 'component' && isPlainObject(definition)) {
              definition.name = definition.name || id;
              definition = this.options._base.extend(definition);
            }
            if (type === 'directive' && typeof definition === 'function') {
              definition = { bind: definition, update: definition };
            }
            this.options[type + 's'][id] = definition;
            return definition
          }
        };
      });
    }

首先要知道,当vue库文件加载完后,vue的初始化中已有这个东西:

Vue.options={
    components:{
        KeepAlive:Object,
        Transition:Object,
        TransitionGroup:Object
    },
    directives:{
        show:Object,
        model:Object
    },
    filter:{},
    _base:function Vue$3(options){...}
}

这些都是vue库内置的组件和指令,当执行Vue.component、Vue.directive、Vue.filter时就是在对这些内置组件、指令、过滤器进行扩充,所以:

    var child=Vue.component('child',{
        template:'<div>child</div>',
        props:['name']
    })

执行完后

    Vue.options.components={
        KeepAlive:Object,
        Transition:Object,
        TransitionGroup:Object,
        child:function VueComponent(options)
    }

child的配置项存放在VueComponent.options中,在该函数中还对props,computed进行了处理:

    //对child的props的处理,key='name'
    proxy(VueComponent.prototype, "_props", key);
        //proxy函数如下:
        function proxy (target, sourceKey, key) {
          sharedPropertyDefinition.get = function proxyGetter () {
            return this[sourceKey][key]
          };
          sharedPropertyDefinition.set = function proxySetter (val) {
            this[sourceKey][key] = val;
          };
          //在VueComponent.prototype定义存取器属性'name'
          Object.defineProperty(target, key, sharedPropertyDefinition);
        }

顺便提一句,当定义全局指令时

    Vue.directive('v-focus',function(){...})
    //会将定义的函数当作bind和update函数,运行完是这样:
    Vue.options.directives={
        show:Object,
        model:Object,
        v-focus:{
            bind:function(){...},
            update:function(){...},
        }
    }

2、详细分析

vue的渲染过程可分为四步:


模板解析.jpg

当child=Vue.component('child',...)执行完后,开始new Vue的过程,经过一系列的处理,得到根节点的AST结构:

    attrs:Array(1) //这里存放了id:'app'
    attrsList:Array(1)
    attrsMap:Object
    children:Array(1)
    parent:undefined
    plain:false
    static:false
    staticRoot:false
    tag:"div"
    type:1
    __proto__:Object
    //其中的children[0],即child的AST如下:
    attrs:Array(1)   //这里存放了name:'message'
    attrsList:Array(1)
    attrsMap:Object
    children:Array(0)
    hasBindings:true
    parent:Object
    plain:false
    static:false
    staticRoot:false
    tag:"child"
    type:1      //得到AST时,vue将child看成普通html标签,未做特殊处理
    __proto__:Object

此时得到的render函数如下:

    with(this){
        return _c(
            'div',
            {attrs:{"id":"app"}},
            [_c(
                'child',
                {attrs:{"name":message}}
                )
            ],
        1)
    }

render函数执行时,message取到了'message from parent',下面主要看_c函数的处理:

    vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
    //createElement函数如下:
    function createElement (context,tag,data,children,normalizationType,alwaysNormalize) {
      if (Array.isArray(data) || isPrimitive(data)) {
        normalizationType = children;
        children = data;
        data = undefined;
      }
      if (alwaysNormalize) { normalizationType = ALWAYS_NORMALIZE; }
      return _createElement(context, tag, data, children, normalizationType)
    }
    function _createElement (context,tag,data,children,normalizationType) {
      ...
      // support single function children as default scoped slot
      //
      ... 暂时略过slot的处理
      //
      var vnode, ns;
      if (typeof tag === 'string') {
        var Ctor;
        ns = config.getTagNamespace(tag);
        if (config.isReservedTag(tag)) {
          // platform built-in elements
          vnode = new VNode(
            config.parsePlatformTagName(tag), data, children,
            undefined, undefined, context
          );
        } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
          //这里取到了child的构造函数
          // 创建child的vnode
          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
          );
        }
      } else {
        // direct component options / constructor
        vnode = createComponent(tag, data, context, children);
      }
      if (vnode) {
        if (ns) { applyNS(vnode, ns); }
        return vnode
      } else {
        return createEmptyVNode()
      }
    }

看下createComponent函数

function createComponent (Ctor,data,context,children,tag) {
  ...
  // 异步组件处理
  ...
  // 处理option
  resolveConstructorOptions(Ctor);
  data = data || {};
  // 将组件v-model的data转成props & events
  ...
  // 提取props
  var propsData = extractProps(data, Ctor, tag);
  // 函数组件
  ...
  // 提取事件监听
  var listeners = data.on;
  // 用.native modifier替换
  data.on = data.nativeOn;
  // 合并组件管理钩子到placeholder vnode
  mergeHooks(data);
  // 返回placeholder vnode
  var name = Ctor.options.name || tag;
  var vnode = new VNode(
    ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
    data, undefined, undefined, undefined, context,
    { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }
  );
  return vnode
}

最终child的Vnode结构如下:

    children:undefined
    componentInstance:undefined
    componentOptions:Object
    context:Vue$3
    data:Object
    elm:undefined
    functionalContext:undefined
    isCloned:false
    isComment:false
    isOnce:false
    isRootInsert:true
    isStatic:false
    key:undefined
    ns:undefined
    parent:undefined
    raw:false
    tag:"vue-component-1-child"
    text:undefined
    child:(...)
    __proto__:Object
    //componentOptions对象如下:
        Ctor:function VueComponent(options)
        children:undefined
        listeners:undefined
        propsData:Object //包含name:'message from parent'
        tag:"child"
        __proto__:Object

组件的Vnode和普通html标签的Vnode的主要差异在componentOptions
在得到child的Vnode后,再得到根节点的Vnode,然后根节点的render函数执行完毕,返回根节点的Vnode(children里存放了child的Vnode)。然后执行vm._update函数,该函数调用vm.__patch__,patch又调用createElm函数:

    function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {
        vnode.isRootInsert = !nested; // for transition enter check
        //注意createComponent函数,对于普通html标签对应的vnode会返回false,但对于组件的vnode会返回true
        if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
          return
        }
        var data = vnode.data;
        var children = vnode.children;
        var tag = vnode.tag;
        if (isDef(tag)) {
          ...
          vnode.elm = vnode.ns
            ? nodeOps.createElementNS(vnode.ns, tag)
            : nodeOps.createElement(tag, vnode);
          setScope(vnode);
          /* istanbul ignore if */
          {
            createChildren(vnode, children, insertedVnodeQueue);
            if (isDef(data)) {
              invokeCreateHooks(vnode, insertedVnodeQueue);
            }
            insert(parentElm, vnode.elm, refElm);
          }
          ...
        }
        ...
      }

首先是处理根节点的Vnode,当执行到createChildren(vnode, children, insertedVnodeQueue)时,开始处理child的Vnode,createChildren是直接调用createElm函数的,所以createElm函数再次执行,不同的是这次处理child的Vnode,来看下createComponent函数:

  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    var i = vnode.data; //
    if (isDef(i)) {
      var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        //这里开始执行vnode.data.hook.init函数
        i(vnode, false /* hydrating */, parentElm, refElm);
      }
      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.
      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue);
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
        }
        return true
      }
    }
  }

看下vnode.data.hook.init函数

    init: function init (vnode,hydrating,parentElm,refElm) {
        if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) {
        //这里开始创建vnode的组件实例
          var child = vnode.componentInstance = createComponentInstanceForVnode(
            vnode,
            activeInstance,
            parentElm,
            refElm
          );
          child.$mount(hydrating ? vnode.elm : undefined, hydrating);
        } else if (vnode.data.keepAlive) {
          // kept-alive components, treat as a patch
          var mountedNode = vnode; // work around flow
          componentVNodeHooks.prepatch(mountedNode, mountedNode);
        }
    }

看下createComponentInstanceForVnode函数

function createComponentInstanceForVnode (
  vnode, // we know it's MountedComponentVNode but flow doesn't
  parent, // activeInstance in lifecycle state
  parentElm,
  refElm
) {
    //  vnode.componentOptions如下:
    //  Ctor:function VueComponent(options)
    //  children:undefined
    //  listeners:undefined
    //  propsData:Object
    //  tag:"child"
    //  __proto__:Object
  var vnodeComponentOptions = vnode.componentOptions;
  var options = {
    _isComponent: true,
    parent: parent,
    propsData: vnodeComponentOptions.propsData,
    _componentTag: vnodeComponentOptions.tag,
    _parentVnode: vnode,
    _parentListeners: vnodeComponentOptions.listeners,
    _renderChildren: vnodeComponentOptions.children,
    _parentElm: parentElm || null,
    _refElm: refElm || null
  };
  // check inline-template render functions
  var inlineTemplate = vnode.data.inlineTemplate;
  if (inlineTemplate) {
    options.render = inlineTemplate.render;
    options.staticRenderFns = inlineTemplate.staticRenderFns;
  }
  //这里的Ctor就是 function VueComponent(options){
  //                    this._init(options);
  //               }
  return new vnodeComponentOptions.Ctor(options)
}

接着就进入了和new Vue()一样的处理过程了

    ...
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');
    //这里没有vm.$options.el还未生成,故不执行
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
    ...

此时createComponentInstanceForVnode函数执行完毕,接着执行vnode.data.hook.init中的 child.$mount(hydrating ? vnode.elm : undefined, hydrating),然后又开始了这个过程:

模板解析.jpg

这个过程结束后vnode.data.hook.init函数执行完毕,接着执行createComponent函数中的 initComponent(vnode, insertedVnodeQueue)函数,然后createComponent函数执行完毕,从而createChildren执行完,然后
执行invokeCreateHooks(vnode, insertedVnodeQueue)和insert(parentElm, vnode.elm, refElm),页面就渲染完毕了。

3、总结component的处理过程

4、props和slot的原理

从component的处理过程不难发现props和slot的原理

4.1 props

  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };

注:这里尚未实现this._props

4.2 slot

    with(this){
        return _c('div',[_v(_s(name)),_t("default")],2)
    }
上一篇下一篇

猜你喜欢

热点阅读