VUE源码分析之props原理

   先来看一个props用法的简单例子:

<div id="app">

        <child-comp

                   :parent-msg = "'here is parent msg'">

        </child-comp>

</div>



Vue.component('child-comp', {

      template: "<div>abc</div>",

       props: ['parentMsg'],

       mounted: function(){

              console.log("parent msg is: " + this.parentMsg);

       }

});

const app = new Vue({

      el: '#app'

});

这个例子很简单,接触过Vue的童鞋肯定能看的懂,props down,  event up  嘛。 父组件通过props属性可以单向向子组件传递数据。而子组件通过event可以向父组件传递数据。那么我的问题来了,通过注释Vue.js中的代码使parentMsg传递数据失效。这就要求我们从源码角度分析这个过程。下面我们具体来分析一下这个过程。

       我们之前分析过注册全局组件过程(VUE源码分析之注册全局组件过程_夜跑者的博客-CSDN博客_vue源码分析) 知道child-comp组件被注册后会生成一个具有以Vue对象原型为原型的对象,且这个组件对象被注册到了Vue.options.components中。child-comp组件对象中具有props属性parentMsg。 源码中肯定有通过Vue.options.components 找到这个child-comp组件对象地方,且对parentMsg进行了赋值。但这个地方在源码中不太好找。我们只好老老实实的一步一步正向分析。

      注册完全局组件后,代码走到了new Vue这里,在实例化Vue对象时调用了_init(options) 函数,函数体里面进行了一些初始化操作:

      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

我们看一下这个initRender 函数:

function initRender (vm) {
    vm._vnode = null; // the root of the child tree
    vm._staticTrees = null; // v-once cached trees
    var options = vm.$options;
    var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree
    var renderContext = parentVnode && parentVnode.context;
    vm.$slots = resolveSlots(options._renderChildren, renderContext);
    vm.$scopedSlots = emptyObject;
    // bind the createElement fn to this instance
    // so that we get proper render context inside it.
    // args order: tag, data, children, normalizationType, alwaysNormalize
    // internal version is used by render functions compiled from templates
    vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
    // normalization is always applied for the public version, used in
    // user-written render functions.
    vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };

    // $attrs & $listeners are exposed for easier HOC creation.
    // they need to be reactive so that HOCs using them are always updated
    var parentData = parentVnode && parentVnode.data;

    /* istanbul ignore else */
    {
      defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
        !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
      }, true);
      defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
        !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
      }, true);
    }
  }

这个函数里面定义了vm._c 函数和vm.$createElement函数,百度出来这两个函数是用来生成VNode节点的,当用户写的组件用template模板时用vm._c , 当用户写的组件用render函数时用vm.$createElement。我们这个例子是用哪个呢?

做完这些初始化后,走到了

     if (vm.$options.el) {
        vm.$mount(vm.$options.el);
      }

看看$mount这个函数,从Vue.js里面搜索看到有两个定义,看第二个定义:

var mount = Vue.prototype.$mount;
  Vue.prototype.$mount = function (
    el,
    hydrating
  ) 

从这几行代码看到先把第一个定义赋值给变量mount ,又重新对Vue.prototype.$mount 进行了定义,所以调用的是这个第二次定义的函数,看这个函数:

       

var ref = compileToFunctions(template, {
          outputSourceRange: "development" !== 'production',
          shouldDecodeNewlines: shouldDecodeNewlines,
          shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        }, this);
        var render = ref.render;
        var staticRenderFns = ref.staticRenderFns;
        options.render = render;
        options.staticRenderFns = staticRenderFns;


这几行代码是把模板编译成render函数的过程,这个过程挺复杂的,先不看了。我们可以通过打印看输入,输出。输入:

<div id="app">

        <child-comp

                   :parent-msg = "'here is parent msg'">

        </child-comp>

</div>

输出了render函数:

function anonymous(){

      with(this){return _c('div', {attrs:{"id":"app"}}, [_c('child-comp', {attrs:{"parent-msg":'here is parent msg'}})], 1)}

}

接下来我们调用第一次定义的$mount函数:return mount.call(this, el, hydrating)。  看这个第一次定义的$mount函数

Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
  };

往下走看mountComponent 这个函数

updateComponent = function () {
        vm._update(vm._render(), hydrating);
      };



new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);

我们先不管Watcher 了 ,接着往下走,走到updateComponent, 又走到vm._render() 了,还记得这个函数吗? 是在initRender 初始化函数时定义的,看这个_render函数: 

vnode = render.call(vm._renderProxy, vm.$createElement);

我们之前分析的render 在这里用到了。 还记得vm._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 (isTrue(alwaysNormalize)) {
      normalizationType = ALWAYS_NORMALIZE;
    }
    return _createElement(context, tag, data, children, normalizationType)
  }

接着往下走,看_createElement这个函数,在这里加上打印会看到tag 为child-comp , data为{"attrs":{"parent-msg": "here is parent msg"}}。 我们进去看看_createElement: 

    if (typeof tag === 'string') {
      var Ctor;
      ns = (context.$vnode && context.$vnode.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 ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
        // component
        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);
    }

我们的tag为“child-comp",且不是html  ReservedTag 所以会走((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag)))

我们看看resolveAsset 这个函数:

  function resolveAsset (
    options,
    type,
    id,
    warnMissing
  ) {
    /* istanbul ignore if */
    if (typeof id !== 'string') {
      return
    }
    var assets = options[type];
    // check local registration variations first
    if (hasOwn(assets, id)) { return assets[id] }
    var camelizedId = camelize(id);
    if (hasOwn(assets, camelizedId)) { return assets[camelizedId] }
    var PascalCaseId = capitalize(camelizedId);
    if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] }
    // fallback to prototype chain
    var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];
    if (warnMissing && !res) {
      warn(
        'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
        options
      );
    }
    return res
  }

从var assets = options[type];      var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];  这两行我们就得到了我们的组件对象,还记得吗? Vue.options.components["child-comp"]

接下来我们看看createComponent(Ctor, data, context, children, tag); 

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

在这个函数体里我们看到了extract props  看看这个函数

function extractPropsFromVNodeData (
    data,
    Ctor,
    tag
  ) {
    // we are only extracting raw values here.
    // validation and default values are handled in the child
    // component itself.
    var propOptions = Ctor.options.props;
    if (isUndef(propOptions)) {
      return
    }
    var res = {};
    var attrs = data.attrs;
    var props = data.props;
    if (isDef(attrs) || isDef(props)) {
      for (var key in propOptions) {
        var altKey = hyphenate(key);
        {
          var keyInLowerCase = key.toLowerCase();
          if (
            key !== keyInLowerCase &&
            attrs && hasOwn(attrs, keyInLowerCase)
          ) {
            tip(
              "Prop \"" + keyInLowerCase + "\" is passed to component " +
              (formatComponentName(tag || Ctor)) + ", but the declared prop name is" +
              " \"" + key + "\". " +
              "Note that HTML attributes are case-insensitive and camelCased " +
              "props need to use their kebab-case equivalents when using in-DOM " +
              "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"."
            );
          }
        }
        checkProp(res, props, key, altKey, true) ||
        checkProp(res, attrs, key, altKey, false);
      }
    }
    return res
  }

在这里checkProp(res, attrs, key, altKey, false);  

  function checkProp (
    res,
    hash,
    key,
    altKey,
    preserve
  ) {
    if (isDef(hash)) {
      if (hasOwn(hash, key)) {
        res[key] = hash[key];
        if (!preserve) {
          delete hash[key];
        }
        return true
      } else if (hasOwn(hash, altKey)) {
        res[key] = hash[altKey];
        if (!preserve) {
          delete hash[altKey];
        }
        return true
      }
    }
    return false
  }

加上打印我们看到了parentMsg:“here is parent msg"    注册的全局组件child-comp中的props属性 parentMsg 终于拿到了父组件中定义的“here is parent msg" 

接下来就是创建VNode了

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 },
      asyncFactory
    );

以propsData挂到VNode上。

父组件通过props把数据传递给子组件整个过程就分析完了。 很复杂,看了好长时间。还记得文章开始处提的问题吗?

通过注释Vue.js中的代码使parentMsg传递数据失效  ? 答:注释掉 checkProp(res, attrs, key, altKey, false); 就可以了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值