The scheduling process of component updates
Component Update Scheduling Process
The component update scheduling process in Vue 3 is one of the core mechanisms of the reactivity system. When reactive data that a component depends on changes, Vue needs to efficiently arrange and execute component updates. This process involves multiple key steps, including dependency collection, update queue management, and asynchronous batch updates.
Reactive Dependencies and Triggering Updates
Component instances access reactive data during rendering, establishing dependency relationships. When data changes, it triggers dependency updates:
const state = reactive({ count: 0 })
watchEffect(() => {
console.log(state.count) // Accessing count establishes a dependency
})
// Modifying count triggers an update
state.count++
Each component instance has a corresponding render function effect
, which is wrapped as a ReactiveEffect
instance. When reactive data changes, these effects
are notified to execute updates.
Implementation of the Update Queue
Vue uses a queue to manage component updates, avoiding redundant calculations and frequent DOM operations. The core implementation is located in scheduler.ts
:
const queue: (Job | null)[] = []
let isFlushing = false
let isFlushPending = false
export function queueJob(job: Job) {
if (!queue.includes(job)) {
queue.push(job)
queueFlush()
}
}
When changes in reactive data may trigger multiple component updates, Vue places all update tasks into the queue instead of executing them immediately.
Asynchronous Batch Update Mechanism
Vue uses microtasks to implement asynchronous batch updates. In browser environments, Promise.resolve().then()
is typically used:
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true
currentFlushPromise = Promise.resolve().then(flushJobs)
}
}
This mechanism ensures that all data changes within the same event loop trigger only one component update, even if multiple properties are modified consecutively:
state.count++
state.name = 'new name'
// Although two properties are modified, only one component update is triggered
Update Priority Handling
Vue 3 assigns priorities to different types of update tasks. For example, user-defined watch
callbacks are executed after component updates by default:
export function queuePostFlushCb(cb: Function) {
if (!isArray(cb)) {
postFlushCbs.push(cb)
} else {
postFlushCbs.push(...cb)
}
queueFlush()
}
Lifecycle hooks also have specific execution timings. The updated
hook is called after the component's DOM is updated:
onUpdated(() => {
console.log('Component has been updated')
})
Component Update Execution Flow
When update tasks begin executing, they go through the following steps:
- Preprocess the queue and sort tasks
- Execute pre-component update tasks
- Execute component rendering updates
- Execute post-update tasks (e.g.,
updated
hook)
The core execution function is simplified as follows:
function flushJobs() {
isFlushPending = false
isFlushing = true
// Sort tasks
queue.sort((a, b) => getId(a!) - getId(b!))
// Execute tasks
for (let i = 0; i < queue.length; i++) {
const job = queue[i]
if (job) {
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
}
}
// Post-update tasks
flushPostFlushCbs()
isFlushing = false
}
Parent-Child Component Update Order
Vue uses a depth-first update strategy, ensuring child components are updated before parent components:
graph TD
A[Parent beforeUpdate] --> B[Child beforeUpdate]
B --> C[Child render]
C --> D[Child updated]
D --> E[Parent render]
E --> F[Parent updated]
This order ensures parent components can access the latest state of child components.
Handling Special Cases
For dynamic components and keep-alive
components, additional logic is applied to update scheduling. For example, keep-alive
skips updates for inactive components:
if (instance.isDeactivated) {
queueJob(instance.update)
return
}
Asynchronous component loading also triggers specific scheduling:
defineAsyncComponent({
loader: () => import('./AsyncComp.vue'),
onLoaded(comp) {
// Trigger parent component update
parentComponent.update()
}
})
Additional Checks in Development Mode
In development mode, Vue performs additional validation:
if (__DEV__) {
checkRecursiveUpdates(seen, fn)
}
This includes detecting potential infinite update loops:
const state = reactive({ count: 0 })
watchEffect(() => {
state.count++ // In development mode, this warns about infinite updates
})
Differences Compared to Vue 2
Vue 3's update scheduling has several key improvements over Vue 2:
- Uses ES6
Promise
instead ofMutationObserver
for microtask queues - More granular priority control
- More flexible update triggering with the Composition API
- Better TypeScript support
For example, Vue 2's nextTick
implementation:
// Vue 2
Vue.nextTick(() => {
// DOM has been updated
})
In Vue 3, it becomes:
import { nextTick } from 'vue'
nextTick(() => {
// More reliable implementation
})
Performance Optimization Strategies
Vue 3 employs multiple optimization techniques in update scheduling:
- Uses
Set
to deduplicate update tasks - Skips unnecessary component updates
- Compile-time static hoisting reduces runtime overhead
- Proxy-based reactivity system reduces dependency collection overhead
For example, the compiler hoists static nodes:
// Before compilation
<div>
<span>Static content</span>
{{ dynamic }}
</div>
// After compilation
const _hoisted = createVNode("span", null, "Static content")
function render() {
return [_hoisted, ctx.dynamic]
}
Error Handling Mechanism
Errors during updates are caught and handled uniformly:
function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
try {
return args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
}
Users can customize error handling via the errorCaptured
hook:
onErrorCaptured((err) => {
console.error('Component update error:', err)
return false // Prevent the error from propagating further
})
Integration with Suspense
When using Suspense
, asynchronous components have special update scheduling:
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
// Handle Suspense boundary
parentSuspense?.registerDep(instance, setupRenderEffect)
}
This ensures the DOM is updated only after all asynchronous dependencies are resolved.
Custom Schedulers
Advanced users can implement custom schedulers:
import { effect, reactive } from 'vue'
const obj = reactive({ foo: 1 })
effect(
() => {
console.log(obj.foo)
},
{
scheduler(effect) {
// Custom scheduling logic
setTimeout(effect.run, 1000)
}
}
)
This is useful for scenarios like animation queues.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn