Differences and Implementation Principles of Vue3's ref and reactive

Differences and Implementation Principles of Vue3's ref and reactive

Problem Description
Vue3's reactivity system provides two core APIs, ref and reactive, to create reactive data. Understanding their differences, applicable scenarios, and underlying implementation principles is crucial for correctly using Vue3 and handling interviews.

Knowledge Explanation

Step 1: Design Purpose and Basic Usage

  1. reactive

    • Design Goal: Used to create a deeply reactive object or array. It proxies the entire object via Proxy, recursively converting all properties of the object into reactive ones.
    • Basic Usage:
      import { reactive } from 'vue';
      
      const state = reactive({
        count: 0,
        user: {
          name: 'Alice'
        }
      });
      
      // Modifying properties automatically updates the view
      state.count++;
      state.user.name = 'Bob'; // Nested properties are also reactive
      
  2. ref

    • Design Goal: Used to create a reactive reference that can wrap any value (including primitive types like string, number, boolean, as well as objects and arrays). It solves the problem that reactive cannot directly proxy primitive types.
    • Basic Usage:
      import { ref } from 'vue';
      
      const count = ref(0); // Wrap a number
      const message = ref('Hello'); // Wrap a string
      const obj = ref({ name: 'Alice' }); // Wrap an object
      
      // Access and modification require the `.value` property
      console.log(count.value); // 0
      count.value++; // Modify
      message.value = 'World';
      obj.value.name = 'Bob'; // Note: Accessing properties on `.value` here
      
    • Automatic Unwrapping in Templates: In templates, you don't need to write .value; Vue automatically unwraps it for you.
      <template>
        <div>{{ count }}</div> <!-- No need to write count.value -->
      </template>
      

Step 2: Core Differences Comparison

Feature ref reactive
Data Type Can wrap any value (primitive types and reference types) Can only be used for object types (Object, Array, Map, Set)
Access/Modify Requires the .value property Can directly access and modify properties
Template Usage Automatic unwrapping, no .value needed Direct property access
TS Type Definition Ref<T> The object itself is reactive, more intuitive typing
Implementation Principle Implemented via getter/setter for the .value property of an object Proxies the entire object via Proxy

Step 3: Underlying Implementation Principle Analysis

  1. Core Implementation of reactive

    • Technology: Based on ES6's Proxy.
    • Process:
      • reactive(obj) returns a Proxy wrapper for obj.
      • Getter Tracking (Dependency Collection): When you read a property of the proxy object (e.g., state.count), the Proxy's get trap function is triggered. Vue executes track(target, key) here, "collecting" the currently running side effect function (e.g., the component's render function) to establish a dependency relationship. This is called dependency collection.
      • Setter Triggering Updates: When you modify a property of the proxy object (e.g., state.count = 1), the Proxy's set trap function is triggered. Vue executes trigger(target, key) here, finding all side effect functions that depend on this property and re-executing them. This is called triggering updates.
    • Recursive Reactivity: In the get trap, if the read property value is itself an object, Vue recursively calls reactive() on it, ensuring nested properties are also reactive.
  2. Core Implementation of ref

    • Technology: Based on property accessors (getter/setter) of a plain object.
    • Process:
      • ref(value) returns a plain object called a "ref object" with the structure { value: ... }.
      • This ref object has a private property _value to store the actual value.
      • It exposes a public value property. Reading and modifying this value property are intercepted by getter and setter.
      • Getter Tracking (Dependency Collection): When you read myRef.value, the getter function is called. Internally, it executes track(ref, 'value'), tracking access to the key 'value'.
      • Setter Triggering Updates: When you set myRef.value = newValue, the setter function is called. Internally, it first updates _value, then executes trigger(ref, 'value'), notifying all dependencies to update.
    • Optimization for ref: If an object is passed to ref, Vue internally automatically calls reactive to deeply process this object. Therefore, ref({}) is equivalent to reactive({}) in terms of reactivity, differing only in the access method (requires .value).

Step 4: How to Choose and Summary

  • Scenarios for using ref:

    • Defining reactive data for primitive types (e.g., strings, numbers, booleans).
    • Defining reactive data whose future value is uncertain. Because ref is more versatile.
    • When returning reactive data in logic reuse (Composables), ref is easier to destructure while maintaining reactivity.
  • Scenarios for using reactive:

    • Defining a set of logically related data, such as a form object, a page state management object. This way, you don't need to add .value before each property, making the code cleaner.
  • Key Points to Remember:

    • The limitation of reactive is that it only works on object types and uses Proxy.
    • The universality of ref comes from its use of getter/setter to wrap the .value property, allowing it to hold any value.
    • Both ultimately integrate into Vue3's unified reactivity system (track and trigger).