Vue.js 源码分析(十三) 基础篇 组件 props属性详解
父组件通过props属性向子组件传递数据,定义组件的时候可以定义一个props属性,值可以是一个字符串数组或一个对象。
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
</head>
<body>
<div id="app"><child :title="message"></child></div>
<script>
Vue.component('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">
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app"><child></child></div>
<script>
Vue.component('child',{
template:'<h1>{{title}}</h1>',props:{title:{default:'Hello World'}} //这里我们定义的title是个对象,含有默认值
})
var app = new Vue({
el:'#app'
})
</script>
</body>
</html>
这里父组件app没有给子组件child传递数据,子组件使用了默认值Hello World,渲染的结果和第一个例子是一样的。
writer by:大沙漠 QQ:22969969
源码分析
以上面的例1为例,Vue.component()注册组件的时候会调用Vue.extend()生成一个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 = key.toLowerCase(); //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 = Object.create(vm.constructor.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 = parentVnode.componentOptions; //占位符VNode初始化传入的配置信息
opts.propsData = vnodeComponentOptions.propsData; //这就是上面经过extractPropsFromVNodeData()得到的propsData对象
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
opts.render = options.render;
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
// during Vue.extend(). 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, prop.type); //调用getTypeIndex()含糊判断prop.type是否包含布尔类型
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, prop.type);
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源码算是有比较大的深入了解了
Vue.js 源码分析(十三) 基础篇 组件 props属性详解的更多相关文章
- Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持).过滤器应该被添加在 JavaScrip ...
- Vue.js 源码分析(五) 基础篇 方法 methods属性详解
methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...
- Vue.js 源码分析(四) 基础篇 响应式原理 data属性
官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...
- Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解
Vue有三个属性和模板有关,官网上是这样解释的: el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 template ;一个字符串模板作为 Vue 实例的标识使用.模板将会 ...
- Vue.js 源码分析(二) 基础篇 全局配置
Vue.config是一个对象,包含Vue的全局配置,可以在启动应用之前修改下列属性,如下: ptionMergeStrategies ;自定义合并策略的选项silent ...
- Vue.js 源码分析(九) 基础篇 生命周期详解
先来看看官网的介绍: 主要有八个生命周期,分别是: beforeCreate.created.beforeMount.mounted.beforeupdate.updated .beforeDes ...
- Vue.js 源码分析(八) 基础篇 依赖注入 provide/inject组合详解
先来看看官网的介绍: 简单的说,当组件的引入层次过多,我们的子孙组件想要获取祖先组件的资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱.这个就是这对选项要干的事情 provide和 ...
- Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解
先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...
- Vue.js 源码分析(十) 基础篇 ref属性详解
ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...
随机推荐
- Log4Net记录日志(mvc)
转自:http://blog.csdn.net/zhoufoxcn/article/details/2220533 感谢:柄棋先生 第一步:下载Log4Net 下载地址:http://logging. ...
- Java编程基础——运算符和进制
Java编程基础——运算符和进制 摘要:本文主要介绍运算符和进制的基本知识. 说明 分类 Java语言支持如下运算符: ◆ 算术运算符:++,--,+,-,*,/,%. ◆ 赋值运算符:=,+=,-= ...
- java基本程序设计结构总结
学习一门语言:(1)掌握它的表现形式(2)这些语言什么应用. 1.1关键字 1.关键字是被赋予了特殊含义的单词. 2.关键字特点:关键字所有字母都小写. 3.类名的每一个单词开头必须大写. 1.2标识 ...
- 构建maven项目,自定义目录结构方法
构建maven项目 创建自定义的文件目录方法: 在项目名称右键-->Builder Path-->Configure Builder Path...Source菜单下的Add Folder ...
- SAP MM 同一个序列号可以被多次用在交货单发货过账?
SAP MM 同一个序列号可以被多次用在交货单发货过账? 如下公司间转储订单,从公司代码CSAS转入公司代码HKCS, 物料有启用序列号管理. 转储数量为5 PC.该STO单据共计有2个外向交货单 8 ...
- ANDROID培训准备资料之BroadcastReceiver
BroacastReceiver的启动方式? (1) 创建需要启动的BroadcastReceiver的Intent. (2) 调用context的sendBroadcast()或者s ...
- Jmeter参数化、检查点、集合点教程
在使用Jemeter做压力测试的时候,往往需要参数化用户名,密码以到达到多用户使用不同的用户名密码登录的目的,这个时候我们就可以使用参数化登录. 一.badboy录制需要的脚本.也可以用fiddler ...
- 基于hashlib下的文件校验
hashlib不仅可以对密码进行加密也可以对文件内容进行校验,传统的小文件校验通过人为校验是不现实的,如果摸个文件里面的内容多出一个空格的话那么哦是根本就不知道的因此我们需要一个可以校验文件的方法,而 ...
- Django 练习班级管理系统八 -- 上传文件
Form表单上传文件 修改 views.py import os def upload(request): if request.method == 'GET': img_list = models. ...
- os模块,sys模块,json和pickle模块,logging模块
目录 OS模块 sys模块 json和pickle模块 序列化和反序列化 json模块 pickle logging模块 OS模块 能与操作系统交互,控制文件 / 文件夹 # 创建文件夹 import ...