vue2/3
Proxy比defineProperty到底好在哪(vue2/3数据响应式实现方式的差异)
Proxy比defineProperty到底好在哪【渡一教育】
- vue2中定义了一个Observer,利用Object.defineProperty来监听对象属性的读取的修改,为了监听对象的每个属性,Observer会对深度遍历每个属性。一旦属性被读取或者修改,Observer就可以在读取或修改时做一些其他操作。但这种监听方式有个弊端,那就是对象新增或者删除属性时,无法监听到。
- Proxy解决了上述问题,Proxy直接监听对象整体,一旦对象发生变化,Proxy就行监听得到。
在Vue2和Vue3中,响应式系统的实现方式有所不同。Vue2使用的是Object.defineProperty
,而Vue3则采用了Proxy
。这两种技术都用于实现数据的响应式,但它们的实现方式和能力有所不同。
1. Vue2中的Object.defineProperty
在Vue2中,响应式系统是通过Object.defineProperty
来实现的。Object.defineProperty
可以定义或修改对象的属性,并且可以设置属性的getter
和setter
,从而在属性被访问或修改时触发相应的操作。
代码示例:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log(`get ${key}: ${val}`);
return val;
},
set: function reactiveSetter(newVal) {
console.log(`set ${key}: ${newVal}`);
if (newVal !== val) {
val = newVal;
}
}
});
}
const data = {};
defineReactive(data, 'message', 'Hello, Vue2!');
console.log(data.message); // 输出: get message: Hello, Vue2!
data.message = 'Hello, World!'; // 输出: set message: Hello, World!
缺点:
- 无法监听数组的变化:
Object.defineProperty
无法直接监听数组的变化(如push
、pop
等操作),Vue2中通过重写数组方法来实现对数组的监听。 - 无法监听属性的添加或删除:
Object.defineProperty
只能监听已经存在的属性,无法监听对象属性的添加或删除。
2. Vue3中的Proxy
Vue3中使用了Proxy
来实现响应式系统。Proxy
是ES6引入的新特性,它可以拦截对象的操作,包括属性的读取、设置、删除等。
代码示例:
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
console.log(`get ${key}: ${target[key]}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`set ${key}: ${value}`);
return Reflect.set(target, key, value, receiver);
},
deleteProperty(target, key) {
console.log(`delete ${key}`);
return Reflect.deleteProperty(target, key);
}
});
}
const data = reactive({ message: 'Hello, Vue3!' });
console.log(data.message); // 输出: get message: Hello, Vue3!
data.message = 'Hello, World!'; // 输出: set message: Hello, World!
delete data.message; // 输出: delete message
优点:
- 监听数组变化:
Proxy
可以直接监听数组的变化,无需像Vue2那样重写数组方法。 - 更全面的拦截能力:
Proxy
可以拦截更多的操作,包括属性的读取、设置、删除、枚举等,而Object.defineProperty
只能拦截属性的读取和设置。 - 性能更好:
Proxy
是原生支持的特性,性能上比Object.defineProperty
更好。 - 更简洁的代码:使用
Proxy
可以更简洁地实现响应式系统,无需像Object.defineProperty
那样需要遍历对象的每个属性。
如何理解响应式?
响应式是指当数据发生变化时,系统能够自动更新依赖于这些数据的部分。在前端框架中,响应式系统通常用于实现数据与视图的自动同步。例如,当数据发生变化时,视图会自动更新,而不需要手动操作DOM。
在Vue中,响应式系统的核心是依赖追踪和触发更新:
- 依赖追踪:当组件渲染时,Vue会记录哪些数据被使用(即依赖)。
- 触发更新:当数据发生变化时,Vue会通知所有依赖该数据的组件进行更新。
Vue2中Observer
是如何实现的?
在Vue2中,响应式系统的核心是Observer
,它通过Object.defineProperty
来实现对数据的监听。
实现步骤:
- 遍历对象属性:
Observer
会遍历对象的每一个属性。 - 定义
getter
和setter
:使用Object.defineProperty
为每个属性定义getter
和setter
。 - 依赖收集:在
getter
中收集依赖(即哪些组件或计算属性使用了该数据)。 - 触发更新:在
setter
中触发更新,通知依赖该数据的组件重新渲染。
代码示例:
function defineReactive(obj, key, val) {
const dep = new Dep(); // 依赖管理器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
dep.depend(); // 收集依赖
return val;
},
set: function reactiveSetter(newVal) {
if (newVal !== val) {
val = newVal;
dep.notify(); // 触发更新
}
}
});
}
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => effect());
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
const data = {};
defineReactive(data, 'message', 'Hello, Vue2!');
watchEffect(() => {
console.log('Effect:', data.message);
});
data.message = 'Hello, World!'; // 输出: Effect: Hello, World!
缺点:
- 无法监听数组的变化(需要重写数组方法)。
- 无法监听属性的添加或删除。
Reflect`是什么?
Reflect
是ES6引入的一个内置对象,它提供了一组用于操作对象的方法。这些方法与Proxy
的拦截器方法一一对应,例如Reflect.get
、Reflect.set
、Reflect.deleteProperty
等。
Reflect
的主要作用是:
- 提供一种更规范的方式来操作对象。
- 与
Proxy
配合使用,确保Proxy
的拦截器方法能够正确地操作目标对象。
Proxy
中为什么要用Reflect
?
在Proxy
的拦截器方法中使用Reflect
是为了确保操作能够正确地应用到目标对象上。例如:
Reflect.get(target, key, receiver)
:确保get
操作能够正确地获取目标对象的属性值。Reflect.set(target, key, value, receiver)
:确保set
操作能够正确地设置目标对象的属性值。
使用Reflect
的好处是:
- 避免手动操作目标对象,减少错误。
- 保持代码的简洁性和一致性。
示例:
const target = { message: 'Hello' };
const proxy = new Proxy(target, {
get(target, key, receiver) {
console.log(`Getting ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`Setting ${key} to ${value}`);
return Reflect.set(target, key, value, receiver);
}
});
proxy.message; // 输出: Getting message
proxy.message = 'World'; // 输出: Setting message to World
Proxy的
get和
set中为什么还有个
receiver`?
receiver
是Proxy
的get
和set
方法的第三个参数,它指向当前的代理对象(或继承代理对象的对象)。
receiver
的作用是:
- 确保
this
的指向正确。例如,当代理对象被继承时,receiver
会指向子类实例,而不是目标对象。 - 与
Reflect
方法配合使用,确保操作能够正确地应用到代理对象上。
示例:
const target = { name: 'Alice' };
const proxy = new Proxy(target, {
get(target, key, receiver) {
console.log(`Getting ${key} from ${receiver === proxy}`);
return Reflect.get(target, key, receiver);
}
});
const child = Object.create(proxy);
child.name; // 输出: Getting name from false
为什么说“Proxy
是原生支持的特性”?
Proxy
是ES6标准的一部分,由JavaScript引擎原生支持。这意味着:
- 不需要额外的库或工具来实现
Proxy
。 - 性能更好,因为它是引擎级别的实现。
- 兼容性较好,现代浏览器和Node.js都支持
Proxy
。
相比之下,Object.defineProperty
虽然也是原生特性,但它的功能有限,无法实现Proxy
那样全面的拦截能力。
【vue2源码】array.ts
路径
src/core/observer/array.ts
代码及注释
/*
* 不对此文件进行类型检查,因为 Flow(类型检查工具)无法很好地处理
* 动态访问数组原型上的方法
*/
// 导入 Vue 3 中的 TriggerOpTypes,用于标识触发更新的操作类型
import {TriggerOpTypes} from '../../v3'
// 导入 Vue 的工具函数 def,用于定义对象的属性
import {def} from '../util/index'
// 获取数组的原型对象
const arrayProto = Array.prototype
// 创建一个新的对象,继承自数组的原型对象
// 这样可以在不直接修改 Array.prototype 的情况下,扩展数组的方法
export const arrayMethods = Object.create(arrayProto)
// 需要拦截的数组变异方法列表
const methodsToPatch = [
'push', // 向数组末尾添加元素
'pop', // 删除数组末尾的元素
'shift', // 删除数组开头的元素
'unshift', // 向数组开头添加元素
'splice', // 从数组中添加/删除元素
'sort', // 对数组进行排序
'reverse' // 反转数组
]
/**
* 拦截数组的变异方法并触发更新事件
*/
methodsToPatch.forEach(function (method) {
// 缓存数组原型上的原始方法
const original = arrayProto[method]
// 使用 def 函数在 arrayMethods 对象上定义新的方法
// 这些方法会覆盖数组原型上的同名方法
def(arrayMethods, method, function mutator(...args) {
// 调用原始的数组方法,并获取返回值
const result = original.apply(this, args)
// 获取当前数组的观察者对象(Observer 实例)
const ob = this.__ob__
// 用于存储新插入的元素
let inserted
// 根据不同的方法,处理新插入的元素
switch (method) {
case 'push':
case 'unshift':
// push 和 unshift 方法会将所有参数作为新元素插入数组
inserted = args
break
case 'splice':
// splice 方法的第三个参数开始是插入的新元素
inserted = args.slice(2)
break
}
// 如果有新插入的元素,调用 observeArray 方法对它们进行观察
if (inserted) ob.observeArray(inserted)
// 通知依赖更新
if (__DEV__) {
// 在开发环境下,传递更详细的更新信息
ob.dep.notify({
type: TriggerOpTypes.ARRAY_MUTATION, // 更新类型为数组变异
target: this, // 目标数组
key: method // 触发更新的方法名
})
} else {
// 在生产环境下,只通知更新
ob.dep.notify()
}
// 返回原始方法的执行结果
return result
})
})
代码解释
-
类型检查注释:
- 代码开头的注释提到,不对此文件进行类型检查,因为 Flow(Vue 2 中使用的类型检查工具)无法很好地处理动态访问数组原型上的方法。
-
导入模块:
TriggerOpTypes
是 Vue 3 中用于标识触发更新的操作类型的枚举。虽然这段代码是 Vue 2 的,但可能引用了 Vue 3 的某些类型定义。def
是 Vue 的工具函数,用于定义对象的属性。
-
数组原型扩展:
arrayProto
是数组的原型对象。arrayMethods
是一个新创建的对象,继承自arrayProto
。这样可以在不直接修改Array.prototype
的情况下,扩展数组的方法。
-
需要拦截的数组方法:
methodsToPatch
是一个数组,列出了需要拦截的数组变异方法。这些方法会改变数组的内容,Vue 需要拦截这些方法以触发视图更新。
-
拦截数组方法:
- 对于
methodsToPatch
中的每个方法,代码都会在arrayMethods
上定义一个同名的方法。 - 这些新定义的方法首先会调用原始的数组方法(
original.apply(this, args)
),然后处理新插入的元素(如果有的话),最后通知依赖更新。
- 对于
-
处理新插入的元素:
- 对于
push
和unshift
方法,所有参数都是新插入的元素。 - 对于
splice
方法,第三个参数开始是插入的新元素。 - 如果有新插入的元素,调用
ob.observeArray(inserted)
对它们进行观察,以便 Vue 能够追踪这些新元素的变化。
- 对于
-
通知依赖更新:
- 在开发环境下,
ob.dep.notify
会传递更详细的更新信息,包括更新类型、目标数组和触发更新的方法名。 - 在生产环境下,只简单地通知依赖更新。
- 在开发环境下,
-
返回原始方法的执行结果:
- 最后,返回原始方法的执行结果,确保新定义的方法与原始方法的行为一致。
总结
这段代码的核心目的是拦截数组的变异方法,并在这些方法被调用时触发 Vue 的响应式系统更新视图。通过这种方式,Vue 能够追踪数组的变化,并在数组内容发生变化时自动更新视图。
【vue2源码】nextTick.ts
nextTick应用场景及示例
在 Vue2 中,nextTick
是一个非常重要的 API,用于在 DOM 更新完成后执行回调。由于 Vue 的响应式更新是异步批量处理的,直接修改数据后立即操作 DOM 可能无法获取更新后的结果。nextTick
可以确保代码在 DOM 更新完成后执行,从而避免操作未更新的 DOM。
🔍 核心应用场景
-
操作更新后的 DOM
当数据变化后需要立即操作新生成的 DOM 元素(如获取元素尺寸、聚焦输入框等)。 -
依赖 DOM 的第三方库初始化
例如使用图表库(ECharts)时,需要确保容器已渲染完成。 -
解决异步更新导致的逻辑问题
当多个数据变化需要在同一事件循环中完成后统一处理。 -
在组件渲染完成后执行逻辑
例如在mounted
钩子中操作子组件的 DOM。
📝 具体示例
示例 1:数据更新后滚动到列表底部
<template>
<div>
<div ref="list" style="height: 100px; overflow-y: auto;">
<div v-for="item in items" :key="item">{{ item }}</div>
</div>
<button @click="addItem">添加条目</button>
</div>
</template>
<script>
export default {
data() {
return { items: [1, 2, 3] };
},
methods: {
addItem() {
this.items.push(this.items.length + 1);
this.$nextTick(() => {
// DOM 更新后滚动到最新条目
const list = this.$refs.list;
list.scrollTop = list.scrollHeight;
});
},
},
};
</script>
说明:
- 点击按钮后,列表会新增一个条目。
- 使用
nextTick
确保 DOM 更新完成后,滚动条滚动到列表底部。
示例 2:输入框自动聚焦
<template>
<div>
<button @click="showInputField">显示输入框</button>
<input v-if="showInput" ref="input" />
</div>
</template>
<script>
export default {
data() {
return { showInput: false };
},
methods: {
showInputField() {
this.showInput = true;
this.$nextTick(() => {
// DOM 渲染完成后聚焦
this.$refs.input.focus();
});
},
},
};
</script>
说明:
- 点击按钮后,输入框显示并自动聚焦。
- 使用
nextTick
确保输入框渲染完成后才调用focus
方法。
示例 3:结合第三方图表库(ECharts)
<template>
<div ref="chart" style="width: 400px; height: 300px;"></div>
</template>
<script>
import echarts from 'echarts';
export default {
data() {
return { data: [5, 20, 36] };
},
mounted() {
this.$nextTick(() => {
// 确保容器 DOM 已渲染
const chart = echarts.init(this.$refs.chart);
chart.setOption({
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ data: this.data, type: 'bar' }],
});
});
},
};
</script>
说明:
- 在
mounted
钩子中使用nextTick
确保 DOM 渲染完成后初始化 ECharts 图表。
示例 4:在子组件渲染完成后操作 DOM
<template>
<div>
<ChildComponent ref="child" />
<button @click="updateChild">更新子组件</button>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: { ChildComponent },
methods: {
updateChild() {
// 更新子组件数据
this.$refs.child.someData = 'new value';
this.$nextTick(() => {
// 子组件 DOM 更新后执行操作
const childElement = this.$refs.child.$el;
console.log('子组件已更新:', childElement);
});
},
},
};
</script>
说明:
- 父组件更新子组件数据后,使用
nextTick
确保子组件 DOM 更新完成后再执行操作。
示例 5:解决多个数据更新后的统一处理
<template>
<div>
<p>{{ message }}</p>
<button @click="updateData">更新数据</button>
</div>
</template>
<script>
export default {
data() {
return { message: 'Hello', count: 0 };
},
methods: {
updateData() {
this.message = 'Updated';
this.count++;
this.$nextTick(() => {
// 确保所有数据更新完成
console.log('DOM 已更新:', this.$el.textContent);
});
},
},
};
</script>
说明:
- 点击按钮后,同时更新多个数据。
- 使用
nextTick
确保所有数据更新完成后执行回调。
示例 6:动态加载组件后操作 DOM
<template>
<div>
<button @click="loadComponent">加载组件</button>
<component :is="currentComponent" ref="dynamicComponent" />
</div>
</template>
<script>
import DynamicComponent from './DynamicComponent.vue';
export default {
data() {
return { currentComponent: null };
},
methods: {
loadComponent() {
this.currentComponent = DynamicComponent;
this.$nextTick(() => {
// 动态组件加载完成后操作 DOM
const componentElement = this.$refs.dynamicComponent.$el;
console.log('动态组件已加载:', componentElement);
});
},
},
};
</script>
说明:
- 动态加载组件后,使用
nextTick
确保组件 DOM 渲染完成后再执行操作。
⚙️ 实现原理
Vue2 的 nextTick
内部基于 微任务/宏任务队列 实现:
- 优先使用
Promise.then
(微任务)。 - 降级到
MutationObserver
或setImmediate
。 - 最终使用
setTimeout(fn, 0)
(宏任务)。
通过异步队列机制,确保回调在本次事件循环的 DOM 更新后执行。
💡 注意事项
- 避免滥用:频繁调用
nextTick
可能导致性能问题。 - 生命周期中的使用:在
mounted
钩子中操作 DOM 时,建议包裹nextTick
,因为子组件可能尚未渲染完成。 - 与
watch
结合使用:在watch
中监听数据变化时,如果需要操作 DOM,可以使用nextTick
。
通过合理使用 nextTick
,可以精准控制 Vue 的异步更新流程,解决 DOM 操作时机问题。
【Vue2】生命周期钩子函数
Vue 2 的生命周期是 Vue 组件从创建到销毁的整个过程,每个阶段都有对应的生命周期钩子函数,你可以在这些钩子中执行特定的逻辑。理解这些钩子函数有助于你在合适的时机执行代码,从而更好地控制组件的行为。
Vue 2 的生命周期钩子函数
Vue 2 的生命周期可以分为以下几个阶段,每个阶段都有对应的钩子函数:
-
创建阶段(Initialization)
beforeCreate
created
-
挂载阶段(Mounting)
beforeMount
mounted
-
更新阶段(Updating)
beforeUpdate
updated
-
销毁阶段(Destruction)
beforeDestroy
destroyed
1. 创建阶段
beforeCreate
- 调用时机:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
- 使用场景:此时组件的
data
和methods
还未初始化,通常用于一些插件或功能的初始化。
beforeCreate() {
console.log('beforeCreate: 组件实例刚被创建,data 和 methods 还未初始化');
}
created
- 调用时机:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,
$el
属性目前尚不可用。 - 使用场景:此时可以访问
data
和methods
,通常用于发起异步请求、初始化数据等。
created() {
console.log('created: 组件实例创建完成,data 和 methods 已初始化');
}
2. 挂载阶段
beforeMount
- 调用时机:在挂载开始之前被调用,相关的
render
函数首次被调用。 - 使用场景:此时模板已经编译完成,但尚未将模板渲染到页面上。
beforeMount() {
console.log('beforeMount: 模板编译完成,但尚未挂载到页面');
}
mounted
- 调用时机:在实例挂载完成后调用,此时
el
被新创建的vm.$el
替换,并挂载到实例上去之后调用该钩子。 - 使用场景:此时 DOM 已经渲染完成,可以操作 DOM 或访问 DOM 元素。
mounted() {
console.log('mounted: 组件已挂载到页面,可以访问 DOM');
}
3. 更新阶段
beforeUpdate
- 调用时机:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
- 使用场景:可以在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
beforeUpdate() {
console.log('beforeUpdate: 数据更新,DOM 还未重新渲染');
}
updated
- 调用时机:由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。
- 使用场景:此时 DOM 已经更新,可以执行依赖于 DOM 的操作。
updated() {
console.log('updated: 数据更新,DOM 已重新渲染');
}
4. 销毁阶段
beforeDestroy
- 调用时机:实例销毁之前调用。在这一步,实例仍然完全可用。
- 使用场景:通常用于清理工作,比如清除定时器、取消事件监听等。
beforeDestroy() {
console.log('beforeDestroy: 组件即将销毁,此时实例仍然可用');
}
destroyed
- 调用时机:实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
- 使用场景:通常用于最后的清理工作。
destroyed() {
console.log('destroyed: 组件已销毁,所有的事件监听器和子实例已被移除');
}
生命周期图示
为了更好地理解 Vue 2 的生命周期,可以参考以下图示:
beforeCreate -> created -> beforeMount -> mounted -> beforeUpdate -> updated -> beforeDestroy -> destroyed
总结
- 创建阶段:
beforeCreate
和created
钩子用于组件的初始化和数据设置。 - 挂载阶段:
beforeMount
和mounted
钩子用于 DOM 的挂载和操作。 - 更新阶段:
beforeUpdate
和updated
钩子用于数据更新时的 DOM 操作。 - 销毁阶段:
beforeDestroy
和destroyed
钩子用于组件的清理和销毁。
【Vue2】父子组件的生命周期有何关联性?
在 Vue 2 中,父子组件的生命周期钩子函数执行顺序遵循明确的规律,理解这些关联性有助于优化组件间的数据传递、DOM 操作和资源管理。以下是父子组件生命周期的关联性及执行顺序的详细总结:
一、父子组件生命周期的执行顺序
1. 加载渲染阶段
- 顺序:
父 beforeCreate → 父 created → 父 beforeMount → 子 beforeCreate → 子 created → 子 beforeMount → 子 mounted → 父 mounted
- 说明:
- 父组件先完成初始化和模板编译(
beforeCreate
、created
、beforeMount
),但此时子组件尚未创建。 - 在父组件的
beforeMount
阶段,子组件开始创建并完成自身的生命周期(beforeCreate
、created
、beforeMount
、mounted
)。 - 父组件的
mounted
在所有子组件挂载完成后触发,确保此时父组件可以安全操作子组件的 DOM。
- 父组件先完成初始化和模板编译(
2. 更新阶段
- 顺序:
父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated
- 说明:
- 父组件数据变化时,先触发父的
beforeUpdate
。 - 子组件接收父组件传递的 props 更新后,执行子组件的
beforeUpdate
和updated
。 - 父组件的
updated
最后执行,确保所有子组件已完成更新。
- 父组件数据变化时,先触发父的
3. 销毁阶段
- 顺序:
父 beforeDestroy → 子 beforeDestroy → 子 destroyed → 父 destroyed
- 说明:
- 父组件销毁时,先触发自身的
beforeDestroy
。 - 子组件依次销毁,触发自身的
beforeDestroy
和destroyed
。 - 父组件的
destroyed
最后执行,确保所有子组件已清理完毕。
- 父组件销毁时,先触发自身的
二、关键关联性分析
1. 父组件是子组件的“容器”
- 创建与挂载:父组件需先完成模板解析(
beforeMount
),才能开始子组件的初始化和挂载。 - 更新与销毁:父组件的状态变化或销毁会触发子组件的响应,体现了父子组件状态的依赖关系。
2. 异步组件的特殊场景
- 如果子组件是异步加载的(如通过
() => import()
),父组件的生命周期会先完成(执行到mounted
),子组件的生命周期后续才执行。
三、实际开发中的注意事项
1. 操作子组件的 DOM
- 在父组件的
mounted
钩子中操作子组件的 DOM 是安全的,因为此时子组件已挂载完成。
2. 异步数据传递
- 若父组件在
created
中异步获取数据并传递给子组件,子组件可能在created
或mounted
中无法立即获取数据。
解决方法:- 使用
v-if
控制子组件的渲染,确保数据就绪后再渲染子组件。 - 子组件通过
watch
监听 props 的变化。
- 使用
3. 资源清理
- 在父组件的
beforeDestroy
中,子组件尚未销毁,可在此通知子组件执行清理逻辑(如移除事件监听或定时器)。
四、总结规律
- 创建与挂载:父组件先初始化,子组件先挂载完成。
- 更新与销毁:父组件先触发,子组件先完成。
- 执行顺序的核心原则:父组件作为容器,需确保子组件的生命周期在其作用域内完成。
通过掌握这些生命周期关联性,可以避免因时机不当引发的 Bug(如操作未挂载的 DOM),并优化父子组件的协作逻辑。具体示例和完整代码可参考相关开发文档或示例源码。