阿里云主机折上折
  • 微信号
Current Site:Index > The implementation mechanism of reactive and ref

The implementation mechanism of reactive and ref

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

In Vue 3's reactivity system, reactive and ref are core APIs designed to make objects and primitive values reactive, respectively. Both rely on Proxy and dependency tracking under the hood but exhibit significant differences in usage scenarios and internal implementations.

Implementation Mechanism of reactive

reactive uses a Proxy to intercept property access (get) and modification (set) operations on the target object. When a property is accessed, the track function collects dependencies; when a property is modified, the trigger function notifies updates. Here’s a simplified 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 oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      if (oldValue !== value) {
        trigger(target, key) // Trigger updates
      }
      return result
    }
  })
}

Deep reactivity is achieved through recursion:

function reactive(target) {
  if (isObject(target)) {
    Object.entries(target).forEach(([key, val]) => {
      if (isObject(val)) target[key] = reactive(val)
    })
  }
  return /* Proxy logic */
}

Special handling for arrays involves intercepting methods like push:

const arrayInstrumentations = {
  push(...args) {
    const res = Array.prototype.push.apply(this, args)
    trigger(this, 'length') // Manually trigger length change
    return res
  }
}

Implementation Mechanism of ref

ref wraps primitive values in an object, using the value property to access the actual value. Its core implementation is as follows:

function ref(value) {
  return {
    get value() {
      track(this, 'value') // Collect dependencies
      return value
    },
    set value(newVal) {
      if (newVal !== value) {
        value = newVal
        trigger(this, 'value') // Trigger updates
      }
    }
  }
}

Optimizations exist for object types:

function ref(value) {
  return isObject(value) 
    ? reactive(value) // Directly convert to reactive
    : createRef(value)
}

Automatic unwrapping in templates is achieved during compilation:

<script setup>
const count = ref(0)
</script>

<template>
  <!-- Compiled as count.value -->
  <button @click="count++">{{ count }}</button>
</template>

Comparison of Differences

Performance-wise, ref is more efficient for primitive values:

// Create 10,000 reactive variables
const refs = Array(10000).fill(0).map(() => ref(0)) // Faster
const reactives = Array(10000).fill(0).map(() => reactive({ value: 0 }))

Type system support differs significantly:

const numRef = ref<number>(0) // Explicit type
const objReactive = reactive<{ foo: string }>({ foo: 'bar' }) // Automatic inference

Behavior with destructuring:

const state = reactive({ x: 1, y: 2 })
const { x } = state // Loses reactivity
const xRef = toRef(state, 'x') // Maintains reactivity

Underlying Dependency Tracking System

The track function establishes dependency relationships:

const targetMap = new WeakMap()
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)
}

The trigger function triggers updates:

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

Handling Special Scenarios

readonly shares part of the logic:

function readonly(target) {
  return new Proxy(target, {
    get: createGetter(true),
    set(target, key) {
      console.warn(`Set operation on key "${key}" failed: target is readonly.`)
      return true
    }
  })
}

Shallow reactivity with shallowReactive:

function shallowReactive(target) {
  return new Proxy(target, {
    get(target, key) {
      track(target, key)
      const res = Reflect.get(target, key)
      return isObject(res) ? res : reactive(res) // No recursive handling
    }
  })
}

Evolution of Reactivity APIs

Reactivity Transform introduced in Vue 3.3:

// Before compilation
let count = $ref(0)
function increment() {
  count++
}

// After compilation
let count = ref(0)
function increment() {
  count.value++
}

Integration with the Composition API:

function useCounter() {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  return { count, double }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.