main.js(入口文件):
import Vue from "vue";
import App from "./app.vue";
new Vue({
el: "#app",
data() {
return {
person: {
sex: "male",
name: "Jack",
},
};
},
template:
"<div><button @click='test'> 测试 </button><App :person='person'/></div>",
components: {App},
methods: {test() {
this.person = {name: "Mike",};
},
},
});
app.vue:
<template>
<div>{{person.name}}<button @click="test"> 测试 </button></div>
</template>
<script>
// app 组件选项
export default {
name: "app",
props: ["person"],
methods: {test() {this.person.name = "Rose";},
},
};
</script>
1. 组件选项中的 props
存在多种格局,最终都会调用 normalizeProps
格式化 props
。
// 格式化 props
function normalizeProps(options, vm) {
var props = options.props;
if (!props) {return;}
var res = {};
var i, val, name;
if (Array.isArray(props)) {
i = props.length;
while (i--) {val = props[i];
if (typeof val === "string") {name = camelize(val);
res[name] = {type: null};
} else if (process.env.NODE_ENV !== "production") {warn("props must be strings when using array syntax.");
}
}
} else if (isPlainObject(props)) {for (var key in props) {val = props[key];
name = camelize(key);
res[name] = isPlainObject(val) ? val : {type: val};
}
} else if (process.env.NODE_ENV !== "production") {
warn(
'Invalid value for option"props": expected an Array or an Object,' +
"but got" +
toRawType(props) +
".",
vm
);
}
options.props = res;
}
2.main.js 中的根组件渲染页面时,先调用 render
函数生成组件内标签节点(VNode 实例)。生成 app 组件标签节点时,读取根组件的数据 person
赋值给 props 属性person
,props
数据保留在组件标签节点上。
读取
person
属性时触发属性的get
办法,收集组件更新的观察者。当批改person
属性时会触发属性的set
办法,告诉组件更新页面。
// main.js 中根组件的 template 生成的 render 函数
function render() {
return _c(
"div",
[_c("button", { on: { click: test} }, [_v("测试")]),
_c(
"App",
{attrs: { person: person} } // app 组件标签上的 props 数据
),
],
1
);
}
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,
}, // propsData 保留了 props 数据
asyncFactory
);
3. 当渲染 app 组件标签时,会依据组件标签节点创立组件,并初始化组件数据。调用 initProps
初始化 props
,遍历组件选项中的 props
属性。依据 app 组件标签上的 props
数据 (propsData
),调用validateProp
,获取属性对应的值。而后应用 defineProperty
定义 props
对象上的属性,增加get
和set
办法,设置 props
属性响应式,同时监听属性变动,当 props
上的属性批改时输入正告提醒。最初组件代理 props
属性,能够在组件上间接拜访 props
属性。
initProps
中定义了props
对象上的属性,未定义属性值中的对象的属性。批改props
对象属性的值时(例:this.person = {name: 'Rose'}
),有正告提醒,但批改值中的对象属性时(例:this.person.name = 'Peter'
),没有正告提醒。然而页面同样会更新,因为属性值来自于父组件中的data
,父组件中的data
设置了响应式。
// 初始化 props,设置 props 属性值
function initProps(vm, propsOptions) {
// propsOptions 组件选项上的 props
var propsData = vm.$options.propsData || {}; // 组件标签上 props 数据
var props = (vm._props = {});
var keys = (vm.$options._propKeys = []);
var isRoot = !vm.$parent;
if (!isRoot) {
// 不是根组件
toggleObserving(false); // 更改设置响应式标识(设置 props 对象属性响应式,不设置对象属性值响应式(对象属性值的响应式可能来自于父组件 data 数据)}
var loop = function (key) {keys.push(key);
var value = validateProp(key, propsOptions, propsData, vm); // 依据组件标签上的 prop 获取对应的值
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production") {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$$1(props, key, value, function () {
// 定义 props 上的属性,设置 get,set 办法。if (!isRoot && !isUpdatingChildComponent) {
warn(
// 如果更改了 props 对象属性的值正告
"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
);
}
});
} else {defineReactive$$1(props, key, value);
}
if (!(key in vm)) {proxy(vm, "_props", key); // props 挂载到组件上
}
};
for (var key in propsOptions) loop(key);
toggleObserving(true);
}
/**
* 获取 props 属性值
*/
function validateProp(
key, // 组件属性字段
propOptions, // 组件属性配置
propsData, // 组件 props 属性值
vm
) {var prop = propOptions[key];
var absent = !hasOwn(propsData, key);
var value = propsData[key];
// boolean casting
var booleanIndex = getTypeIndex(Boolean, prop.type);
if (booleanIndex > -1) {if (absent && !hasOwn(prop, "default")) {value = false; // Boolean 类型未赋值且没有默认值,则默认为 false} else if (value === "" || value === hyphenate(key)) {
// 值为空或者和属性名雷同
// only cast empty string / same name to boolean if
// boolean has higher priority Boolean 类型领有较高的优先级
var stringIndex = getTypeIndex(String, prop.type);
if (stringIndex < 0 || booleanIndex < stringIndex) {
// 类型不包含字符串或者 Boolean 类型靠前(优先)value = true;
}
}
}
// check default value
if (value === undefined) {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);
}
if (
process.env.NODE_ENV !== "production" &&
// skip validation for weex recycle-list child component props
!false
) {assertProp(prop, key, value, vm, absent);
}
return value;
}
4. 初始化 data
、props
等 app 组件数据后,开始渲染页面。调用 render
时创立标签节点,顺次读取了 props
属性上的 person
和 name
属性。依据创立的标签节点生成 DOM 元素,增加到页面中。
读取
person
属性时,app 组件props
中的person
属性会收集 app 组件更新的观察者。读取name
属性时,根组件 data 中的name
属性会收集 app 组件更新的观察者。
// app 组件 template 生成的 render 函数
function render() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c("div", [_vm._v(_vm._s(_vm.person.name)), // 顺次读取了 props 属性上的 person 和 name 属性
_c("button", { on: { click: _vm.test} }, [_vm._v("测试")]),
]);
}
5. 点击根组件中的按钮触发 test 函数,批改 person
时,将会触发根组件页面更新,更新过程中调用 updateChildComponent
更新子组件标签上的数据,批改 props
的属性 person
,触发 set
办法,告诉 app 组件也更新页面,app 组件将依据批改后的 props 属性值渲染页面。
function updateChildComponent (
vm,
propsData,
listeners,
parentVnode,
renderChildren
) {if (process.env.NODE_ENV !== 'production') {isUpdatingChildComponent = true;// 标识阐明正在更新组件}
...
vm.$attrs = parentVnode.data.attrs || emptyObject;
vm.$listeners = listeners || emptyObject;
// update props
if (propsData && vm.$options.props) {toggleObserving(false);
var props = vm._props;
var propKeys = vm.$options._propKeys || [];
for (var i = 0; i < propKeys.length; i++) {var key = propKeys[i];
var propOptions = vm.$options.props; // wtf flow?
props[key] = validateProp(key, propOptions, propsData, vm);// 更新 props 属性值
}
toggleObserving(true);
// keep a copy of raw propsData
vm.$options.propsData = propsData;
}
...
if (process.env.NODE_ENV !== 'production') {isUpdatingChildComponent = false;}
}
6. 点击 app 组件中的按钮触发 test
函数,批改 person
上的 name
属性,触发 name
属性上的 set
办法,告诉 app 组件更新页面。
在根组件内批改
person
的name
属性,和在 app 组件内批改person
的name
属性是一样的,都会触发 app 组件页面更新。