阿里云主机折上折
  • 微信号
Current Site:Index > The scheduling process of component updates

The scheduling process of component updates

Author:Chuan Chen 阅读数:29137人阅读 分类: Vue.js

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:

  1. Preprocess the queue and sort tasks
  2. Execute pre-component update tasks
  3. Execute component rendering updates
  4. 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:

  1. Uses ES6 Promise instead of MutationObserver for microtask queues
  2. More granular priority control
  3. More flexible update triggering with the Composition API
  4. 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:

  1. Uses Set to deduplicate update tasks
  2. Skips unnecessary component updates
  3. Compile-time static hoisting reduces runtime overhead
  4. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.