Vue.js源码分析(⼗三)基础篇组件props属性详解
⽗组件通过props属性向⼦组件传递数据,定义组件的时候可以定义⼀个props属性,值可以是⼀个字符串数组或⼀个对象。
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="cdn.jsdelivr/npm/vue@2.5.16/dist/vue.js"></script>
</head>
<body>
<div id="app"><child :title="message"></child></div>
<script>
Vueponent('child',{
template:'<h1>{{title}}</h1>',props:['title']       //这⾥props是⼀个字符串数组
})
var app = new Vue({
el:'#app',data:{message:'Hello World'}
})
</script>
</body>
</html>
这⾥我们给child这个组件定义了名为title的props,⽗组件通过title特性传递给⼦组件,渲染为:
props除了数组,也可以是⼀个对象,此时对象的键对应的props的名称,值⼜是⼀个对象,可以包含如下属性:
type:        ;类型,可以设置为:String、Number、Boolean、Array、Object、Date等等            ;如果只设置type⽽未设置其他选项,则值可以直接⽤类型,例如:props:{title:Object}
default      ;默认值
required    ;布尔类型,表⽰是否必填项⽬
validator    ;⾃定义验证函数
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
js合并两个数组<script src="cdn.jsdelivr/npm/vue@2.5.16/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app"><child></child></div>
<script>
Vueponent('child',{
template:'<h1>{{title}}</h1>',props:{title:{default:'Hello World'}}                //这⾥我们定义的title是个对象,含有默认值
})
var app = new Vue({
el:'#app'
})
</script>
</body>
</html>
这⾥⽗组件app没有给⼦组件child传递数据,⼦组件使⽤了默认值Hello World,渲染的结果和第⼀个例⼦是⼀样的。
源码分析
以上⾯的例1为例,Vueponent()注册组件的时候会调⽤d()⽣成⼀个Vue基础构造器,内部会调⽤mergeOptions函数合并属性,mergeOptions⼜会调⽤normalizeProps对props的属性进⾏⼀些规范化的修饰,如下:
function normalizeProps (options, vm) {      //第1361⾏规范化props属性
var props = options.props;                        //尝试获取props属性
if (!props) { return }
var res = {};
var i, val, name;
if (Array.isArray(props)) {                      //如果props是个数组;这是props的数组⽤法的分⽀
i = props.length;
while (i--) {                                    //遍历props
val = props[i];
if (typeof val === 'string') {                  //如果值是⼀个字符串
name = camelize(val);
res[name] = { type: null };                      //保存到res⾥⾯                  ;例如:{ title: {type: null} }
} else {
warn('props must be strings when using array syntax.');
}
}
} else if (isPlainObject(props)) {                //如果props是个对象 ;这是props的对象⽤法的分⽀
for (var key in props) {
val = props[key];
name = camelize(key);
res[name] = isPlainObject(val)
val
:
{ type: val };
}
} else {
warn(
"Invalid value for option \"props\": expected an Array or an Object, " +
"but got " + (toRawType(props)) + ".",
vm
);
}
options.props = res;
}
经过normalizeProps规范后,props被修饰为⼀个对象格式,例⼦⾥的执⾏到这⾥等于:
接下来_render函数执⾏遇到该组件时会执⾏createComponent函数,该函数⼜会执⾏extractPropsFromVNodeData(data, Ctor, tag)函数,如下: function extractPropsFromVNodeData (      //第2109⾏获取原始值
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;                  //获取组件的定义的props对象,例如:{message: {type: null}}
if (isUndef(propOptions)) {
return
}
var res = {};
var attrs = data.attrs;                                  //获取data的attrs属性,例如:{title: "Hello Vue"}
var props = data.props;                                  //获取data的props属性,这应该是建⽴⽗⼦组件时的关系
if (isDef(attrs) || isDef(props)) {                      //如果data有定义了attrs或者props属性
for (var key in propOptions) {                            //遍历组件的props属性
var altKey = hyphenate(key);
{
var keyInLowerCase = LowerCase();                //hyphenate:如果key是是驼峰字符串,则转换为-格式
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优先从props⾥拿对应的属性,其次从attrs⾥拿(对于attrs的话第五个参数为false,即会删除对应的attrs⾥的属性)      checkProp(res, attrs, key, altKey, false);
}
}
return res
}
checkProp是检测props或attrs是否含有key对应的值,如下:
function checkProp (          //第2150⾏检测prop是否存在
res,
hash,
key,
altKey,
preserve
) {
if (isDef(hash)) {              //如果hash存在
if (hasOwn(hash, key)) {            //如果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//如果在res⾥未到则返回false
}
extractPropsFromVNodeData只是获取值,验证理验证和默认值是⼦组件完成执⾏的,执⾏到这⾥就获取到了props的值,例⼦⾥执⾏到这⾥等于
整个对象会作为propsData属性保存到组件的VNode⾥⾯,如下:
当⼦组件实例化的时候会执⾏_init()函数,⾸先会执⾏initInternalComponent函数,对于props的操作如下:
function initInternalComponent (vm, options) {          //第4632⾏⼦组件初始化⼦组件
var opts = vm.$options = structor.options);    //组件的配置信息
// doing this because it's faster than dynamic enumeration.
var parentVnode = options._parentVnode;                            //该组件的占位符VNode
opts.parent = options.parent;
opts._parentVnode = parentVnode;
opts._parentElm = options._parentElm;
opts._refElm = options._refElm;
var vnodeComponentOptions = parentVnodeponentOptions;          //占位符VNode初始化传⼊的配置信息
opts.propsData = vnodeComponentOptions.propsData;                  //这就是上⾯经过extractPropsFromVNodeData()得到的propsData对象
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (der) {
opts.staticRenderFns = options.staticRenderFns;
}
}
这样组件实例化时就得到了propsData了,如下
然后回到_init()初始化函数,会执⾏initState()函数,该函数⾸先会判断是否有props属性,如果有则执⾏initProps初始化props,如下:
function initProps (vm, propsOptions) {    //第3319⾏初始化props属性
var propsData = vm.$options.propsData || {};                      //获取propsData属性,也就是例⼦⾥的{title:"Hello World"}
var props = vm._props = {};
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
var keys = vm.$options._propKeys = [];                            //⽤于保存当前组件的props⾥的key  ;以便之后在⽗组件更新props时可以直接使⽤数组迭代,⽽不需要动态枚举键值var isRoot = !vm.$parent;
// root instance props should be converted
if (!isRoot) {
toggleObserving(false);
}
var loop = function ( key ) {                                    //定义⼀个loop函数,⼀会⼉会循环调⽤它
keys.push(key);                                                  //保存key
var value = validateProp(key, propsOptions, propsData, vm);      //执⾏validateProp检查propsData⾥的key值是否符合propsOptions⾥对应的要求,并将值保存到value⾥⾯
/* istanbul ignore else */
{
var hyphenatedKey = hyphenate(key);
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
vm
);
}
defineReactive(props, key, value, function () {                //将key变成响应式,同时也定义了props的key属性的值为value
if (vm.$parent && !isUpdatingChildComponent) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
}
// static props are already proxied on the component's prototype
// d(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, "_props", key);
}
};
for (var key in propsOptions) loop( key );                          //遍历每个props 依次调⽤loop()函数
toggleObserving(true);
}
⾄此整个流程跑完了,前⾯说了extractPropsFromVNodeData只是获取值,⽽验证理验证和默认值就是在validateProp()函数内做的判断,如下: function validateProp (        //第1582⾏检查props
key,
propOptions,
propsData,
vm
) {
var prop = propOptions[key];                    //获取对应的值,例如:{type: null}
var absent = !hasOwn(propsData, key);          //如果propsData没有key这个键名,则absent为true
var value = propsData[key];                    //尝试获取propsData⾥key这个键的值
// boolean casting
var booleanIndex = getTypeIndex(Boolean, pe);          //调⽤getTypeIndex()含糊判断pe是否包含布尔类型
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, 'default')) {
value = false;
} else if (value === '' || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
var stringIndex = getTypeIndex(String, pe);
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true;
}
}
}
// check default value
if (value === undefined) {                        //如果value未定义
value = getPropDefaultValue(vm, prop, key);        //尝试获取默认值
// since the default value is a fresh copy,
// make sure to observe it.
var prevShouldObserve = shouldObserve;
toggleObserving(true);
observe(value);
toggleObserving(prevShouldObserve);
}
{
assertProp(prop, key, value, vm, absent);          //判断Prop是否有效
}
return value                                      //最后返回value
}
剩下来就⼏个⼯具函数了,⽐较简单,⼤致如此。
注:在Vue这么多属性⾥⾯,props是最有意思,最好玩的。虽然props的⽤法⽐较简单,但是它的原理实现我觉得是最复杂的,理解了props的实现原理,可以说是对Vue源码算是有⽐较⼤的深⼊了解了

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。