先来看一个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); 就可以了。