寫在前面
此系列來源于開源項目:前端 100 問:能搞懂 80%的請把簡歷給我
為了備戰(zhàn) 2021 春招
每天一題,督促自己
從多方面多角度總結答案是掰,豐富知識
在 Vue 中虑鼎,子組件為何不可以修改父組件傳遞的 Prop,如果修改了键痛,Vue 是如何監(jiān)控到屬性的修改并給出警告的炫彩。
簡書整合地址:前端 100 問
正文回答
- 子組件為何不可以修改父組件傳遞的
Prop
?
- 一個父組件下不只有你一個子組件絮短。同樣江兢,使用這份 prop 數據的也不只有你一個子組件。如果每個子組件都能修改 prop 的話丁频,將會導致修改數據的源頭不止一處杉允。
- 單向數據流,易于監(jiān)測數據的流動席里,出現了錯誤可以更加迅速的定位到錯誤發(fā)生的位置叔磷。
- 如果修改了,Vue 是如何監(jiān)控到屬性的修改并給出警告的奖磁。
// 在initProps的時候改基,在defineReactive時通過判斷是否在開發(fā)環(huán)境
// 如果是開發(fā)環(huán)境,會在觸發(fā)set的時候判斷是否此key是否處于updatingChildren中被修改
// 如果不是署穗,說明此修改來自子組件寥裂,觸發(fā)warning提示
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 () {
if (!isRoot && !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
);
}
});
}
需要特別注意的是,當你從子組件修改的
prop
屬于基礎類型時會觸發(fā)提示案疲。這種情況下封恰,你是無法修改父組件的數據源的, 因為基礎類型賦值時是值拷貝褐啡。你直接將另一個非基礎類型(Object, array)賦值到此 key 時也會觸發(fā)提示(但實際上不會影響父組件的數據源)诺舔, 當你修改object
的屬性時不會觸發(fā)提示,并且會修改父組件數據源的數據。
vue 源碼
// src/core/instance/state.js 源碼路徑
function initProps(vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {};
const props = (vm._props = {});
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = (vm.$options._propKeys = []);
const isRoot = !vm.$parent;
// root instance props should be converted
if (!isRoot) {
toggleObserving(false);
}
for (const key in propsOptions) {
keys.push(key);
const value = validateProp(key, propsOptions, propsData, vm);
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production") {
const 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, () => {
if (!isRoot && !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
);
}
});
} else {
defineReactive(props, key, value);
}
// 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);
}
}
toggleObserving(true);
}
// src/core/observer/index.js
/**
* Define a reactive property on an Object.
*/
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep();
const property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
// cater for pre-defined getter/setters
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== "production" && customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) return;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
},
});
}