Lesson 15-Vue3 Source Code Architecture

Vue 3’s source architecture has undergone a complete overhaul compared to Vue 2, adopting more modern programming paradigms and underlying implementations. This section provides an in-depth analysis of Vue 3’s core source architecture, covering the reactive system, virtual DOM, and component system, revealing its design philosophy and implementation principles.

Vue Reactive System Source Code

Dependency Collection and Tracking

Vue 3’s dependency collection system is based on the Effect mechanism, offering a simpler and more efficient approach compared to Vue 2’s Dep/Watcher system:

// Simplified effect implementation
let activeEffect = null

function effect(fn) {
  activeEffect = fn
  fn() // Execute function to trigger getter
  activeEffect = null
}

function track(target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

Dependency relationship flowchart:

Component render functioneffect capturestrack collects dependenciessetter triggerstrigger executes updates

Data Hijacking Implementation

Vue 3 uses Proxy instead of Vue 2’s Object.defineProperty, achieving more comprehensive reactive interception:

// Simplified reactive implementation
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key) // Dependency collection
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      trigger(target, key) // Trigger update
      return result
    },
    // Other trap methods...
  })
}

Proxy vs. defineProperty Comparison:

FeatureProxydefineProperty
Interception CapabilitySupports all operationsOnly get/set
Array HandlingNative supportRequires array method overrides
PerformanceMore efficientLower
CompatibilityES6+ES5+

Array Reactive Handling

Vue 3 directly intercepts array operations via Proxy, eliminating the need to override array methods as in Vue 2:

const arr = reactive([1, 2, 3])
arr.push(4) // Automatically triggers reactive update

Underlying implementation principle:

  1. Proxy intercepts array methods like push
  2. Executes native array operations
  3. Triggers setter to notify dependency updates

Computed Properties Implementation

Computed properties are implemented based on effect and caching mechanisms:

function computed(getter) {
  let value
  let dirty = true
  const effectFn = effect(getter, {
    lazy: true,
    scheduler: () => {
      dirty = true
      trigger(obj, 'value') // Trigger dependency update
    }
  })

  return {
    get value() {
      if (dirty) {
        value = effectFn()
        dirty = false
        track(obj, 'value') // Collect dependencies
      }
      return value
    }
  }
}

Caching mechanism features:

  1. Recalculates only when dependencies change
  2. Reuses results when multiple components share the same computed property
  3. Automatically tracks dependency relationships

Asynchronous Update Queue

Vue 3’s nextTick implementation is based on a microtask queue:

const callbacks = []
let pending = false

function nextTick(fn) {
  callbacks.push(fn)
  if (!pending) {
    pending = true
    Promise.resolve().then(flushCallbacks)
  }
}

function flushCallbacks() {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

Update process:

  1. Data change triggers setter
  2. Adds component update function to the queue
  3. Waits for current synchronous code to complete
  4. Executes updates in the microtask queue

Vue Virtual DOM Source Code

Virtual DOM Creation and Rendering

Virtual DOM creation process:

function h(type, props, children) {
  return {
    type,
    props,
    children,
    key: props?.key,
    el: null // Corresponding real DOM
  }
}

// Render function example
render() {
  return h('div', { class: 'container' }, [
    h('h1', null, 'Hello Vue3'),
    h('p', null, 'Virtual DOM example')
  ])
}

Diff Algorithm Core Logic

Vue 3’s Diff algorithm uses a two-ended comparison strategy:

function patchChildren(n1, n2, container) {
  const c1 = n1.children
  const c2 = n2.children
  
  if (typeof c2 === 'string') {
    // Text node handling
  } else if (Array.isArray(c2)) {
    // New children are an array
    if (Array.isArray(c1)) {
      // Old children are also an array, perform Diff
      patchKeyedChildren(c1, c2, container)
    } else {
      // Old children are not an array, directly replace
    }
  }
}

function patchKeyedChildren(c1, c2, container) {
  let i = 0
  const l2 = c2.length
  let e1 = c1.length - 1
  let e2 = l2 - 1
  
  // Compare from the start
  while (i <= e1 && i <= e2) {
    const n1 = c1[i]
    const n2 = c2[i]
    if (isSameVNodeType(n1, n2)) {
      patch(n1, n2, container)
    } else {
      break
    }
    i++
  }
  
  // Compare from the end
  while (i <= e1 && i <= e2) {
    const n1 = c1[e1]
    const n2 = c2[e2]
    if (isSameVNodeType(n1, n2)) {
      patch(n1, n2, container)
    } else {
      break
    }
    e1--
    e2--
  }
  
  // Handle new nodes
  if (i > e1 && i <= e2) {
    // ...
  }
  
  // Handle deleted nodes
  if (i > e2 && i <= e1) {
    // ...
  }
}

Patch Algorithm Implementation

Node update strategy:

function patch(n1, n2, container) {
  if (n1 && !isSameVNodeType(n1, n2)) {
    // Different types, directly replace
    unmount(n1)
    mount(n2, container)
  } else if (n2.patchFlag) {
    // Targeted updates based on patchFlag
    switch (n2.patchFlag) {
      case PatchFlags.TEXT:
        // Only update text content
        if (n2.el.textContent !== n2.children) {
          n2.el.textContent = n2.children
        }
        break
      case PatchFlags.CLASS:
        // Only update class
        patchClass(n2.el, n2.props.class)
        break
      // Other optimization flags...
    }
  } else {
    // Full comparison
    mount(n2, container, n1)
  }
}

Static Node Hoisting

Static node hoisting optimization example:

// Before compilation
<div>
  <h1>Static Title</h1>
  <p>{{ dynamicContent }}</p>
</div>

// After compilation
const _hoisted_1 = /*#__PURE__*/h('h1', null, 'Static Title')

function render() {
  return h('div', [
    _hoisted_1,
    h('p', null, dynamicContent)
  ])
}

Benefits of hoisting:

  1. Static nodes are created only once
  2. Skips Diff comparison process
  3. Reduces memory usage

Virtual DOM Performance Optimization

Key optimization strategies:

  1. Patch Flags: Marks dynamic nodes during compilation
  2. Block Tree: Skips comparison for static subtrees
  3. Longest Increasing Subsequence: Optimizes node movement operations
  4. Cached Event Handlers: Avoids repeated function creation

Vue Component System Source Code

Component Creation and Mounting Process

Core component mounting process:

function mountComponent(vnode, container) {
  // 1. Create component instance
  const instance = createComponentInstance(vnode)
  
  // 2. Set up component state
  setupComponent(instance)
  
  // 3. Create render proxy
  setupRenderEffect(instance, container)
}

function setupRenderEffect(instance, container) {
  instance.update = effect(() => {
    if (!instance.isMounted) {
      // Initial render
      const subTree = (instance.subTree = instance.render())
      patch(null, subTree, container, instance)
      instance.isMounted = true
    } else {
      // Update render
      const nextTree = instance.render()
      patch(instance.subTree, nextTree, container, instance)
      instance.subTree = nextTree
    }
  })
}

Component Communication Implementation

Props passing mechanism:

function initProps(instance, rawProps) {
  instance.props = reactive(rawProps)
}

// When parent updates props
function updateProps(instance, newProps) {
  const oldProps = instance.props
  instance.props = reactive(newProps)
  // Trigger dependency update...
}

Event emission system:

function emit(instance, event, ...args) {
  const { props } = instance
  const handlerName = `on${capitalize(event)}`
  const handler = props[handlerName]
  if (handler) {
    callWithAsyncErrorHandling(handler, instance, ErrorCodes.COMPONENT_EMIT, args)
  }
}

Slot Rendering Mechanism

Slot implementation principle:

function renderSlot(slots, name, props) {
  const slot = slots[name]
  if (slot) {
    if (typeof slot === 'function') {
      return createVNode(Fragment, {}, slot(props))
    } else {
      return slot
    }
  }
  return createCommentVNode('v-slot')
}

Scoped slot implementation:

// Compile-time processing
const _ctx = this
h('div', [
  _ctx.$slots.default({
    text: _ctx.text
  })
])

// Runtime processing
function renderSlot(slots, name, props) {
  const slot = slots[name]
  if (typeof slot === 'function') {
    return createVNode(Fragment, {}, slot(props))
  }
  // ...
}

Dynamic Component Implementation

Dynamic component switching logic:

function resolveDynamicComponent(component) {
  if (typeof component === 'string') {
    return resolveAsset(COMPONENTS, component)
  } else if (component) {
    return component
  }
  return null
}

// When switching components
function patchComponent(n1, n2, container) {
  const instance = (n2.component = n1 ? n1.component : createComponentInstance(n2))
  if (shouldUpdateComponent(n1, n2)) {
    updateComponentPreRender(instance, n2)
  } else {
    instance.update()
  }
}

Component Lifecycle Implementation

Lifecycle hook registration:

const componentLifecycleHooks = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeUnmount',
  'unmounted'
]

function callHooks(instance, hook) {
  const handlers = instance.hooks[hook]
  if (handlers) {
    for (let i = 0; i < handlers.length; i++) {
      handlers[i].call(instance.proxy)
    }
  }
}

// Call at appropriate times
function mountComponent() {
  callHooks(instance, 'beforeCreate')
  // ...initialization logic
  callHooks(instance, 'created')
  // ...mounting logic
  callHooks(instance, 'mounted')
}

Composition API lifecycle mapping:

Options APIComposition API
beforeCreatesetup()
createdsetup()
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted

By deeply analyzing Vue 3’s source architecture, we gain a profound understanding of its design philosophy and implementation principles. This foundational knowledge is crucial for writing high-performance, maintainable Vue applications and provides a solid basis for contributing to the Vue ecosystem.

Share your love