
vue2基础
约 1730 字大约 6 分钟
2024-07-26
基础 API
inject
在组件初始化执行完 callHook(vm, 'beforeCreate')
后首先执行 initInjections(vm)
. 执行过程如下:
- 遍历 inject, 逐级向父组件查找是否存在对应的 provide, 生成一个 obj, key 是 inject 的元素, value 是父组件提供的 provide
- 将生成的这个 obj 执行 defineReactive,变成响应式。
export function resolveInject(inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null);
const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
// #6574 in case the inject object is observed...
if (key === "__ob__") continue;
const provideKey = inject[key].from;
let source = vm;
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey];
break;
}
source = source.$parent;
}
if (!source) {
if ("default" in inject[key]) {
const provideDefault = inject[key].default;
result[key] =
typeof provideDefault === "function"
? provideDefault.call(vm)
: provideDefault;
} else if (process.env.NODE_ENV !== "production") {
warn(`Injection "${key}" not found`, vm);
}
}
}
return result;
}
}
props
- 这里 props 的原理如 injection, 对 props 做一次遍历, 同时去获取对应的 value,这个 value 值的来源在 组件的 propsData 属性上, 这个属性是从父组件传过来生成的
- 将 props 对象执行 defineReactive,变成响应式。
propsData 的来源:是在生成 vNode 的时候,会生成一个 componentOptions 对象, 属性父组件传过来的一些 props。
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);
}
methods
这里简单, 做 methods 对象进行遍历, 将 vm 对象作为 this 绑定到函数中。
function initMethods(vm: Component, methods: Object) {
const props = vm.$options.props;
for (const key in methods) {
if (process.env.NODE_ENV !== "production") {
if (typeof methods[key] !== "function") {
warn(
`Method "${key}" has type "${typeof methods[
key
]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
);
}
if (props && hasOwn(props, key)) {
warn(`Method "${key}" has already been defined as a prop.`, vm);
}
if (key in vm && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
);
}
}
vm[key] =
typeof methods[key] !== "function" ? noop : bind(methods[key], vm);
}
}
data
对于 data 的观察 实质 就是对 返回的 data 对象的观察, 同时做遍历,按照 对象,原始值、数组的方式进行 观察。关键是: observe(data, true /_ asRootData _/)
function initData(vm: Component) {
let data = vm.$options.data;
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
if (!isPlainObject(data)) {
data = {};
process.env.NODE_ENV !== "production" &&
warn(
"data functions should return an object:\n" +
"https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function",
vm
);
}
// proxy data on instance
const keys = Object.keys(data);
const props = vm.$options.props;
const methods = vm.$options.methods;
let i = keys.length;
while (i--) {
const key = keys[i];
if (process.env.NODE_ENV !== "production") {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
);
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== "production" &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
);
} else if (!isReserved(key)) {
proxy(vm, `_data`, key);
}
}
// observe data
observe(data, true /* asRootData */);
}
computed
计算属性本质上就是一个 computed watcher,他同 vue 组件作为一个 watcher 一样,他在初始化过程中会触发 data 中数据哥 getter, 这个时候他作为一个 computed watcher 作为 data 数据的一个 dep 收集起来。
当前 data 中的数据触发 setter 时, 就会通知到 computed watcher 执行对应的 update 方法, 去获取对应的数据, 如果最后获取到的数据跟之前的没有变化, 也不会重新执行 这个函数,生成新的 value。
function initComputed(vm: Component, computed: Object) {
// $flow-disable-line
const watchers = (vm._computedWatchers = Object.create(null));
// computed properties are just getters during SSR
const isSSR = isServerRendering();
for (const key in computed) {
const userDef = computed[key];
const getter = typeof userDef === "function" ? userDef : userDef.get;
if (process.env.NODE_ENV !== "production" && getter == null) {
warn(`Getter is missing for computed property "${key}".`, vm);
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else if (process.env.NODE_ENV !== "production") {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(
`The computed property "${key}" is already defined as a prop.`,
vm
);
}
}
}
}
以上是创建一个 computed watcher , 同时遍历 定义 computed 中的 key: defineComputed(vm, key, userDef)
export function defineComputed(
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering();
if (typeof userDef === "function") {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop;
sharedPropertyDefinition.set = userDef.set || noop;
}
if (
process.env.NODE_ENV !== "production" &&
sharedPropertyDefinition.set === noop
) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value;
}
};
}
以上是获取值的过程
watch
defineReactive
这里整体思路是: data 中的属性值, 如果是原始类型, 则进行一个依赖收集; 如果是对象或者数组, 则需要对里面的子元素进行依赖收集, 然后是深度收集,一次遍历。最终落脚在原始值, 如果是数组则走另外一条道,dependArray(value)
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i];
e && e.__ob__ && e.__ob__.dep.depend();
if (Array.isArray(e)) {
dependArray(e);
}
}
}
- let childOb = !shallow && observe(val) ; 这里先观察传入的 val,如果 val 是对象或数组则会再次执行 ob = new Observer(value),会把这个 val 当做一个观察对象,进行 defineReactive. 这里是一个深度遍历模式
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor(value: any) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, "__ob__", this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk(obj: Object) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}
/**
* Observe a list of Array items.
*/
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
}
}
上面在对 val 为数组做观察时 ,会执行 protoAugment(value, arrayMethods), 这是给这个对象原型上改造后的数据方法,使得在使用 push 等方法可以触发 响应
- 然后依次进行依赖收集,各自手机各自的依赖。
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();
},
});
}
vue2 diff
版权所有
版权归属:tuyongtao1