Vue3 composable APIs

I. Reactive Basic API: ref /reactive (underlying framework + usage)

1. Laying the groundwork for core principles

Vue 3’s reactive system is based on  ES6 Proxy (which replaces Vue 2’s Object.defineProperty), and its core is to “intercept data read/modification operations, trigger dependency collection and view updates”:

  • reactive: Directly create a proxy for the object/array to intercept the reading and writing of properties;
  • ref: It wraps primitive types (String/Number/Boolean) into an ” .value object with properties”, and then proxies this object through a Proxy .

This is why ref it’s necessary .value—essentially, it proxies .value the properties of the wrapper object, rather than the original value itself.

2. ref: Basic type + compatible with complex types

  • Prioritize handling primitive types (String/Number/Boolean/undefined/null);
  • Compatible with complex types (objects/arrays): It automatically converts them reactiveto proxies internally.
Simplified underlying structure
// Simplified implementation logic of ref
function ref(initialValue) {
//
Create a wrapper object with a. value attribute
const wrapper = {
value: initialValue
}
//
Create a proxy for the packaged object to intercept the reading and writing of. value
return new Proxy(wrapper, {
get(target, key) {
if (key === 'value') {
track(target, 'get', key) //
Dependency collection
return target[key]
}
},
set(target, key, newValue) {
if (key === 'value') {
target[key] = newValue
trigger(target, 'set', key) //
Trigger update
return true
}
}
})
}
Example of layered usage
(1) Basic usage: basic types
<script setup>
import { ref } from 'vue'

// 1.
Define basic types of responsive data
const username = ref('admin') // String
const age = ref(18) // Number
const isStudent = ref(true) // Boolean
const emptyVal = ref(null) // null

// 2.
Access/modification:. value must be added to the logic
const updateUser = () => {
username.value = 'admin' //
Triggers responsive update
age.value += 2 // 18 → 20
isStudent.value = false //
Switch states
}

// 3.
emplate: No need for. value (Vue automatically parses wrapper objects)
</script>

<template>
<p>username:{{ username }}</p>
<p>age:{{ age }}</p>
<button @click="updateUser">
Modify Information</button>
</template>
(2) Advanced usage: Complex types (objects/arrays)
<script setup>
import { ref } from 'vue'

//
Define complex types (automatically converted to reactive internally)
const user = ref({
name: 'admin',
address: {
city: 'new york'
}
})
const hobbyList = ref(['
Basketball', 'Game'])

//
Modify complex types: first obtain the reactive object through. value, and then manipulate its properties
const updateUserInfo = () => {
user.value.name = 'admin' //
Modify properties directly (responsive)
user.value.address.city = 'new york' //
Deep attributes are also supported
hobbyList.value.push('
travel ') // Array methods (push/pop/slice, etc.)
}

//
/Replace the entire object (responsive without loss)
const replaceUser = () => {
user.value = {
name: 'admin',
address: { city: 'new york' }
}
}
</script>
Tips for avoiding pitfalls
  • Do not directly override the ref object: age = 20 (This will result in the loss of the Proxy and failure of reactivity), it must be used age.value = 20;
  • Complex types .valueare reactive objects; modifications to deeper properties do not require re-entry .value.
  • TypeScript automatically infers types, eliminating the need for additional definition (e.g., const age = ref(18)automatically infers a type Ref<number>).

3. reactive: Only for complex types (objects/arrays)

It is specifically designed for handling objects/arrays , allowing you to directly create a proxy without needing to manually create one .value, which is more intuitive.

Simplified underlying structure
// Simplified implementation logic of reactive
function reactive(target) {
//
Only effective for objects/arrays, basic types return directly
if (typeof target !== 'object' || target === null) {
return target
}
//
Create a proxy agent to intercept attribute read and write operations
return new Proxy(target, {
get(target, key) {
track(target, 'get', key) //
Dependency collection
//
Deep object recursive proxy (such as user. address. city)
const result = Reflect.get(target, key)
return typeof result === 'object' ? reactive(result) : result
},
set(target, key, newValue) {
Reflect.set(target, key, newValue) //
Set properties
trigger(target, 'set', key) //
Trigger update
return true
}
})
}
Example of layered usage
(1) Basic usage: object
<script setup>
import { reactive } from 'vue'

//
Define object type responsive data
const user = reactive({
name: 'admin',
age: 18,
isStudent: true
})

//
Modify data: Directly manipulate attributes (without the need for. value)
const updateUser = () => {
user.name = 'admin'
user.age = 20
}
</script>
(2) Advanced usage: array + deep objects
<script setup>
import { reactive } from 'vue'

//
Array type
const todoList = reactive([
{ id: 1, text: '
Learn Vue3', done: false },
{ id: 2, text: '
Mastering Composite APIs', done: false }
])

//
Deep objects
const company = reactive({
name: 'Vue',
address: {
city: 'new york',
detail: '
350 5th Avenue'
},
departments: [
'Front end ',' Back end ',' Product ']
})

//
Array operation (responsive)
const addTodo = () => {
todoList.push({ id: 3, text: '
Practical Project', done: false })
}
const toggleTodo = (id) => {
const todo = todoList.find(item => item.id === id)
todo.done = !todo.done
}

//
Deep Object Modification (Responsive)
const updateAddress = () => {
company.address.city = 'new york'
company.departments.push('
Design ')
}
</script>
Tips for avoiding pitfalls
  • Do not use with basic types: const age = reactive(18)(Returns the primitive value 18, non-responsive);
  • Do not replace the entire object: user = { name: 'admin' }(This will override the Proxy and disable reactivity); only modify properties.
  • Automatic recursive proxy for deep objects: No need to manually handle nested properties;
  • Added support for reactive properties: user.gender = 'Male' (Required in Vue2 Vue.set, not required in Vue3).

4. The Ultimate Guide to Choosing Between Ref and Reactive

ScenePreferred APIreason
Primitive types (String/Number/Boolean)refreactive does not support primitive types
Simple objects (few properties, no need for destructuring)reactiveNo need for .value, concise code
Complex objects (requiring destructuring or replacement of the entire object)refDeconstruction without losing responsiveness, supports direct replacement
TypeScript DevelopmentrefType inference is more user-friendly (e.g. Ref<number>)
Array typeBoth are acceptable.ref is needed .value; reactive is more intuitive.

II. Responsive Derived API: computed (caching + linkage + two-way binding)

The system generates “derived values” based on reactive data and caches the results (avoiding recalculation when dependencies remain unchanged), supporting both “read-only” and “write” modes.

Underlying principles

computed: Internally, a “dependency tracking + caching mechanism” is maintained:

  1. Upon first access to computeda value, the computed function is executed, and dependencies are collected (e.g. count);
  2. When the dependent data changes, it is marked computedas “dirty” and recalculated on the next access;
  3. When dependencies remain unchanged, return the cached result directly to improve performance.

Example of layered usage

(1) Basic usage: Read-only computed properties (most commonly used)
<script setup>
import { ref, computed } from 'vue'

//
Raw responsive data
const count = ref(0)
const price = ref(100)
const discount = ref(0.8)

//
Read only calculation attribute: dependent on count/price/discountnt
const totalPrice = computed(() => {
console.log('
Calculate total price (executed only when dependent on changes)')
return (count.value * price.value * discount.value).toFixed(2)
})

//
Multiple visits to total price, execute calculation function only for the first time/when
const logTotal = () => {
console.log(totalPrice.value) //
When the dependency remains unchanged, directly return the cached value
console.log(totalPrice.value) //
Do not execute calculation function
}
</script>

<template>
<p>
Quantity:{{ count }}</p>
<p>
Unit price:{{ price }}</p>
<p>
Discount:{{ discount }}</p>
<p>
Total Price:{{ totalPrice }}</p> <!-- Directly used in the template, no need for .value -->
<button @click="count++">
Increase quantity</button>
<button @click="logTotal">
Print Total Price</button>
</template>
(2) Advanced usage: Computed properties can be written (two-way binding)

It supports reversing the original responsive data by modifying computed properties, which is suitable for scenarios such as “form linkage”.

<script setup>
import { ref, computed } from 'vue'

//
Original data: surname+given name
const firstName = ref('
Michael')
const lastName = ref('Smith')

//
Write calculation attribute: Full name (get, set modify)
const fullName = computed({
//
Get: Generate full name based on firstName/lastName
get() {
return `${firstName.value}${lastName.value}`
},
//
Set: When modifying the full name, split it in reverse to firstName/lastName
set(newValue) {
//
Assuming the input format is "Name" (2 characters)
if (newValue.length === 2) {
firstName.value = newValue[0]
lastName.value = newValue[1]
}
}
})

//
Modify calculation properties and trigger the set method
const updateFullName = () => {
fullName.value = '
Michael Smith' // will trigger set, firstName='Michael ', lastName='Smith'
}
</script>

<template>
<p>full name:{{ fullName }}</p>
<p>first name:{{ firstName }}</p>
<p>last name:{{ lastName }}</p>
<input v-model="fullName" placeholder="
input full name"> <!-- binding -->
<button @click="updateFullName">
Change to Michael Smith</button>
</template>
(3) Practical scenario: Filtering + sorting list
<script setup>
import { ref, computed } from 'vue'

//
Raw data: list+filtering criteria
const list = ref([
{ name: 'Vue3', type: '
Frontend', score: 95 },
{ name: 'React', type: 'Frontend', score: 90 },
{ name: 'Node.js', type: '
Backend', score: 88 },
{ name: 'Python', type: '
Backend', score: 92 }
])
const filterType = ref('all') //
Filter type: all/frontend/backend
const sortBy = ref('score') //
Sort field: score/name

//
Calculation attribute: filtered+sorted list
const filteredList = computed(() => {
// 1.
filter
let result = list.value.filter(item => {
return filterType.value === 'all' ? true : item.type === filterType.value
})
// 2.
sort
result.sort((a, b) => {
if (sortBy.value === 'score') {
return b.score - a.score //
Score in descending order
} else {
return a.name.localeCompare(b.name) //
Name in ascending order
}
})
return result
})
</script>

<template>
<div>
<select v-model="filterType">
<option value="all">
All</option>
<option value="Frontend">
Frontend</option>
<option value="
Backend">Backend</option>
</select>
<select v-model="sortBy">
<option value="score">
Sort by score</option>
<option value="name">
Sort by name</option>
</select>
<ul>
<li v-for="item in filteredList" :key="item.name">
{{ item.name }}({{ item.type }})-
Score:{{ item.score }}
</li>
</ul>
</div>
</template>

Tips for avoiding pitfalls

  • Do not perform side-effect operations (such as modifying data, requesting interfaces, or printing logs) in computed; use them only for calculating derived values.
  • The dependent data must be reactive (ref/reactive), otherwise computed data will not be updated;
  • Writable computed properties must have both get and set configured; otherwise, an error will occur when modifying them.
  • Complex computational logic is recommended to be extracted into a separate function, which is only called in computed (to keep it concise).

III. Responsive Listener APIs: watch /watchEffect /watchPostEffect /watchSyncEffect

The core difference between listening for changes in reactive data and executing logic lies in the “method of specifying the listening source” and the “time of execution”.

1. Core Comparison Table (Note the conclusions first)

APIListening source specifiedTiming of ExecutionOld value acquisitionDependency collectionApplicable Scenarios
watchManually specify (e.g., count, ()=>user.name)Execute synchronously (default)Support (newVal, oldVal)Listen only to specified sourcesRequires old values ​​and precise monitoring of single/multiple sources.
watchEffectAutomatically collect reactive data used in callbacks.Execute synchronously (default)Not supportedAutomatic dependency collectionNo need for old values, listen to multiple scattered sources
watchPostEffectAutomatic collectionExecute after DOM updateNot supportedAutomatic dependency collectionExecution logic needs to be based on the updated DOM.
watchSyncEffectAutomatic collectionSynchronous execution (forced)Not supportedAutomatic dependency collectionThis needs to be executed synchronously (e.g., preparation before modifying the DOM).

2. Watch: Precise monitoring (supports old values ​​+ multiple sources)

Underlying principles

By manually specifying the listener source, Vue will track changes in the source data and execute a callback when the source changes. It supports configurations such as “deep listening” and “immediate execution”.

Example of layered usage
(1) Basic usage: Listening to a single ref data
<script setup>
import { ref, watch } from 'vue'

const count = ref(0)

//
Monitor individual ref data
watch(count, (newVal, oldVal) => {
console.log(`
count changes from ${oldVal} to ${newVal}`)
}, {
immediate: true, //
Execute immediately upon component mounting (default false)
deep: false //
Basic types do not require deep listening
})
</script>
(2) Advanced usage 1: Listening to reactive objects (deep listening)
<script setup>
import { reactive, watch } from 'vue'

const user = reactive({
name: 'admin',
address: {
city: 'new york',
area: '
350 5th Avenue'
}
})

//
Monitor the entire reactive object (automatic deep monitoring, can omit deep: true)
watch(user, (newUser, oldUser) => {
//
Note: newUser and oldUser are the same object (because Proxy proxies the original object)
console.log('
user changed:', newUser.address.city)
}, {
immediate: false,
deep: true //
The listener must enable deep listening (default is true)
})

//
Accurately monitor individual attributes of objects (with better performance and no need for deep monitoring)
watch(
() => user.address.city, //
Listening function (returns the properties to be listened to)
(newCity, oldCity) => {
console.log(`
City changes from ${oldCity} to ${newCity}`)
}
)
</script>
(3) Advanced usage 2: Monitoring multiple data sources
<script setup>
import { ref, reactive, watch } from 'vue'

const count = ref(0)
const user = reactive({ name: 'admin' })

//
Listening to multiple sources: array format
watch(
[count, () => user.name], //
First source:count,second source:user.name
([newCount, newName], [oldCount, oldName]) => {
console.log(`count: ${oldCount}→${newCount},name: ${oldName}→${newName}`)
},
{ immediate: true }
)
</script>
Tips for avoiding pitfalls
  • When monitoring the reactive object, newVal and oldVal are the same object (because Proxy proxies the original object), and the state before the change cannot be obtained through oldVal (manual caching is required);
  • When monitoring a single property of an object, using a “monitor function” ( () => user.address.city) provides better performance;
  • Basic types do not require deep monitoring, but objects/arrays must be enabled deep: true (enabled by default when monitoring the entire object);
  • When executed immediately ( immediate: true), the oldVal value is the first value executed undefined.

3. watchEffect: Automatically collects dependencies (simple and efficient)

Underlying principles

Without needing to specify a listener source, Vue will automatically collect all reactive data used in the callback function as a dependency, and re-execute the callback when any dependency changes.

Example of layered usage
(1) Basic usage: Automatically collect dependencies
<script setup>
import { ref, reactive, watchEffect } from 'vue'

const count = ref(0)
const user = reactive({ name: 'admin' })

//
Automatically collect dependencies: count and user.name
watchEffect(() => {
console.log(`count: ${count.value},name: ${user.name}`)
})

//
Modifying any dependency will trigger a callback
const updateData = () => {
count.value++ //
triggers callback
// user.name = 'admin2' //
will also trigger a callback
}
</script>
(2) Advanced usage 1: Stop listening + clean up side effects
<script setup>
import { ref, watchEffect } from 'vue'

const inputVal = ref('')

//
WatchEffect returns the stop function
const stopWatch = watchEffect((onInvalidate) => {
//
Simulate interface requests (side effects)
const timer = setTimeout(() => {
console.log('
search:', inputVal.value)
}, 500)

//
Cleanup function: executed during dependency changes/component uninstallation (to avoid duplicate requests)
onInvalidate(() => {
clearTimeout(timer)
})
})

//
Manually stop monitoring
const stop = () => {
stopWatch()
}
</script>

<template>
<input v-model="inputVal" placeholder="
input search content">
<button @click="stop">
Stop listening</button>
</template>
(3) Advanced usage 2: Execution timing configuration (flush)
<script setup>
import { ref, watchEffect } from 'vue'

const count = ref(0)

// 1.
Default (flush:'sync'): Synchronized execution (triggered immediately by data changes)
watchEffect(() => {
console.log('
ynchronous Execution:', count.value)
})

// 2.
Flush: 'post': Execute after DOM update (equivalent to watchPostEffect)
watchEffect(() => {
//
Can obtain updated DOM nodes
console.log('
Execute after DOM update:', document.getElementById('count').innerText)
}, { flush: 'post' })

// 3.
Flush: 'pre': Execute before component update (rarely used)
watchEffect(() => {
console.log('
Execute before component update:', count.value)
}, { flush: 'pre' })
</script>

<template>
<div id="count">{{ count }}</div>
<button @click="count++">+1</button>
</template>
Tips for avoiding pitfalls
  • The callback function must “directly use reactive data” (e.g. count.value), otherwise dependencies cannot be collected;Side effect cleanup is mandatory onInvalidate(it cannot be done outside of callbacks) to ensure that old side effects are cleaned up first when dependencies change.
  • Unable to retrieve the old value; if you need the old value, you need to use [the old value watch].
  • The listener will automatically stop when the component is uninstalled, without needing to be called manually stop(unless it needs to be stopped in advance).

4. watchPostEffect / watchSyncEffect: Simplify execution timing

Yes, it’s watchEffect syntactic sugar, no configuration required flush:

  • watchPostEffect =  watchEffect({ flush: 'post' }): Executes after the DOM is updated, suitable for manipulating the updated DOM;
  • watchSyncEffect =  watchEffect({ flush: 'sync' }): Forces synchronous execution, suitable for scenarios that require immediate response.
<script setup>
import { ref, watchPostEffect, watchSyncEffect } from 'vue'

const count = ref(0)

//
Execute after DOM update
watchPostEffect(() => {
console.log('
DOM updated:', document.getElementById('count').innerText)
})

//
Synchronous execution
watchSyncEffect(() => {
console.log('
Synchronous Execution:', count.value)
})
</script>

IV. Responsive Helper APIs: toRef / toRefs / toRaw / unref

Solving problems such as “attribute manipulation, destructuring, and primitive value retrieval of reactive data” is a “utility class” API used in daily development.

1. toRef: A reactive reference to a single property

Create a reactive reference for a single property reactive of an object , maintaining a “reference association” with the original object (modification will synchronously modify the original object, and vice versa).

Usage example
<script setup>
import { reactive, toRef } from 'vue'

const user = reactive({
name: 'admin',
age: 18
})

//
Create a responsive reference for user.name
const nameRef = toRef(user, 'name')

//
Modify nameRef and update the original object synchronously
nameRef.value = 'admin2'
console.log(user.name) //
Output:admin2

//
Modify the original object and update nameRef synchronously
user.name = 'admin3'
console.log(nameRef.value) // Output:admin3
</script>
Applicable Scenarios
  • You only need to use a specific property of an object and want it to remain reactive (without destructuring the entire object).
  • When passing parameters to a component, only a single property of the object is passed (to maintain reactive association).
  • Create a reference to a non-existent property (no error will be thrown, and the property will be added during subsequent assignments): const genderRef = toRef(user, 'gender') //user.gender does not exist, it will not error genderRef.value=’male’. //user.gender is added as ‘male’ and will take effect in response JavaScript

2. toRefs: Batch conversion of reactive properties

This method converts all properties reactive of an object into a single object in batches, returning a regular object, thus solving the problem of “loss of reactivity in destructuring objects”.

Usage example
<script setup>
import { reactive, toRefs } from 'vue'

const user = reactive({
name: 'admin',
age: 18,
address: { city: 'new york' }
})

//
/Batch conversion to ref objects
const userRefs = toRefs(user)
// userRefs
structure:{ name: Ref('admin'), age: Ref(18), address: Ref(...) }

//
Maintain responsiveness even after deconstruction
const { name, age, address } = userRefs

//
Add .value when modifying
name.value = 'admin2' // user.name
synchronized update
address.value.city = 'new york' // user.address.city
synchronized update
</script>
Tips for avoiding pitfalls

 toRefs: It does not create new reactive data; it only “references” the original properties.

  • When a new property is added to the original object, toRefsit will not be automatically synchronized (it needs to be added manually toRef).
  • It is suitable for use in conjunction with destructuring assignment to make the code more concise (no need to write it every time user.name).

3. toRaw: Retrieves the primitive value of the reactive object.

Get reactive or ref wrap the original object/value . Modifying the original value will not trigger a reactive update (suitable for temporary modifications and performance optimization).

Usage example
<script setup>
import { reactive, ref, toRaw } from 'vue'

// 1.
Handling reactive objects
const user = reactive({ name: 'admin' })
const rawUser = toRaw(user) //
Get the original object

//
Modifying original values: will not trigger responsive updates
rawUser.name = 'admin2'
console.log(user.name) //
Output:admin2 (the original object will also change, but there is no responsive trigger)

// 2.
Processing ref objects (requires accessing .value first)
const count = ref(0)
const rawCount = toRaw(count.value) //
ref needs to first take..value
rawCount = 10 //
No response, view not updated

// 3.
Practical scenario: Batch modification of data (to avoid multiple triggering of updates)
const list = reactive([1, 2, 3, 4, 5])
const rawList = toRaw(list)
//
Batch modify the original array, triggering only one update (if directly modifying the list, it will trigger multiple updates)
rawList.push(6, 7, 8)
</script>
Applicable Scenarios
  • Batch modification of reactive objects (avoiding multiple update triggers and improving performance);
  • Passing data to third-party libraries (the third-party libraries do not need to be reactive, avoiding compatibility issues caused by proxy wrapping);
  • Temporary data modifications (without requiring view updates, such as log printing or data validation).

4. unref: Simplifies access to ref values.

Syntactic sugar: If the parameter is refan object, return it value; otherwise, return the parameter itself (avoiding manual judgment isRef).

Usage example
<script setup>
import { ref, unref } from 'vue'

const count = ref(0)
const num = 10

//
Equivalent to: const val1=isRef (count)? count.value : count
const val1 = unref(count) //
Output:0

//
Equivalent to: const val2=isRef (num)? num.value : num
const val2 = unref(num) //
Output:10

//
Practical scenario: Function parameters support ref and normal values
const add = (a, b) => {
return unref(a) + unref(b)
}

console.log(add(count, num)) // 0 + 10 = 10
console.log(add(5, num)) // 5 + 10 = 15
</script>

V. Data Protection API: readonly / shallowReadonly

Create “read-only” reactive objects, prohibiting modification of properties (protecting core data from accidental tampering), and supporting “deep read-only” and “shallow read-only”.

Underlying principles

By intercepting setoperations through a proxy, when attempting to modify attributes, a warning is thrown in the development environment, while the operation fails silently in the production environment (attributes are not modified).

Usage example

<script setup>
import { reactive, readonly, shallowReadonly } from 'vue'

const user = reactive({
name: 'admin',
address: {
city: 'new york',
area: '
350 5th Avenue'
}
})

// 1.
Deep read-only: All attributes at all levels cannot be modified
const readOnlyUser = readonly(user)

readOnlyUser.name = 'admin' //
Development environment warning: Cannot modify read-only property
readOnlyUser.address.city = 'new york' //
Development environment warning: deep properties cannot be modified either

// 2.
Shallow read-only: Only top-level attributes cannot be modified, while deep level attributes can be modified
const shallowUser = shallowReadonly(user)

shallowUser.name = 'admin' //
Development environment warning: top-level properties cannot be modified
shallowUser.address.city = 'new york' //
Success: Deep attributes can be modified (no warning)

// 3.
Read only ref object (directly wrap ref with readonly)
const count = ref(0)
const readOnlyCount = readonly(count)
readOnlyCount.value = 10 //
Development environment warning
</script>
Applicable Scenarios
  • Data passed to child components that you do not want the child components to modify (such as global configurations and static data);
  • Protect the original data returned by the interface (to prevent accidental tampering; if modification is required, a copy can be created based on the original data).
  • Shared state (such as some state in Pinia) can only be modified through specific methods; direct modification of properties is not allowed.

VI. Performance Optimization APIs: shallowRef / shallowReactive

Create “shallow” reactive objects that only listen for changes to top-level properties (changes to deep properties do not trigger reactivity) to optimize the performance of deep data structures (avoiding the overhead of deep proxy).

Key differences (compared to ref/reactive)

APIListening layerTriggering update conditionsApplicable Scenarios
refdeepAttribute changes at any levelBasic types and complex types that need to monitor deep changes.
shallowRefshallow.valueWhen replacing onlyDeep data structures only require replacing the entire object.
reactivedeepAttribute changes at any levelSimple objects that need to listen for deep changes
shallowReactiveshallowOnly top-level attributes changeFor deep data structures (such as large data lists), it is only necessary to listen to the top-level attribute.

Usage example

1. shallowRef: Only listens for .value replacements
<script setup>
import { shallowRef } from 'vue'

//
Shallow ref: only listens for substitution of. value
const user = shallowRef({
name: 'admin',
address: { city: 'new york' }
})

//
Trigger responsive update (replace the entire .value)
user.value = { name: 'admin', address: { city: 'new york' } }

//
Do not trigger responsive updates (modify deep attributes)
user.value.address.city = 'new york'

//
Manually trigger updates (if monitoring for deep changes)
import { triggerRef } from 'vue'
const updateDeep = () => {
user.value.address.city = 'new york'
triggerRef(user) //
Manually trigger responsive updates
}
</script>
2. shallowReactive: Only listens to the top-level property.
<script setup>
import { shallowReactive } from 'vue'

//
Shallow reactive: only listens to top-level properties
const list = shallowReactive([
{ id: 1, name: 'Vue3', detail: { score: 95 } },
{ id: 2, name: 'React', detail: { score: 90 } }
])

//
Trigger responsive update (modify top-level properties)
list[0].name = 'Vue3.4'
list.push({ id: 3, name: 'Node.js' })

//
Do not trigger responsive updates (modify deep attributes)
list[0].detail.score = 98

//
Manually trigger updates (if monitoring for deep changes)
import { triggerReactivity } from 'vue'
const updateDeep = () => {
list[0].detail.score = 98
triggerReactivity(list[0].detail) //
Manually triggered
}
</script>
Applicable Scenarios
  • For large data lists (such as tabular data, 1000+ records): only add, delete, modify and query list items, without needing to monitor changes in the internal attributes of list items;
  • Deeply nested configuration objects: only the entire configuration needs to be replaced, without needing to listen to deep properties within the configuration;
  • Complex objects returned by third-party libraries: No reactive listeners are needed; the entire object only needs to be replaced occasionally.

VII. Component Communication API: provide /inject

Solve the problem of “deep component communication” (such as grandparent-grandchild components, cross-level components), and props achieve “cross-level data sharing” without passing data layer by layer.

Underlying principles

  • provide: Register a “dependency provider” in the parent component to store data/methods in the current component’s “dependency injection context”;
  • inject: Retrieve the corresponding data/methods from the “dependency injection context” in child components, regardless of how deep the hierarchy is.

Example of layered usage

(1) Basic usage: Transmitting ordinary data
<!-- Top level components (such as App. vue): Provider -->
<script setup>
import { provide } from 'vue'

// Provide regular data (non responsive)
provide('appName', 'Vue3 Demo')
provide('version', '3.4.0')
</script>
<!-- Deep sub components (such as GrandChild. vue): User -->
<script setup>
import { inject } from 'vue'

//
Inject data (the second parameter is the default value)
const appName = inject('appName', '
default name')
const version = inject('version', '1.0.0')
const author = inject('author', '
unknown') // No provider, use default value
</script>

<template>
<p>
Application Name:{{ appName }}</p>
<p>
Version:{{ version }}</p>
<p>
Author:{{ author }}</p>
</template>
(2) Advanced usage: Passing reactive data + methods
<!--  Top level component: Provider -->
<script setup>
import { ref, provide } from 'vue'

//
Responsive data
const theme = ref('light') //
Theme:light/dark

//
Method: Modify the theme
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}

//
Provide responsive data and methods
provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script>
<!-- Deep Sub Component: User -->
<script setup>
import { inject } from 'vue'

//
Injecting responsive data and methods
const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>

<template>
<div :class="`app theme-${theme.value}`">
<p>
Current theme:{{ theme.value }}</p>
<button @click="toggleTheme">
Switch Theme</button>
</div>
</template>

<style>
.theme-light { background: #fff; color: #333; }
.theme-dark { background: #333; color: #fff; }
</style>
(3) Advanced usage: Provide read-only data (to prevent child components from modifying it)
<!-- Top level component: Provider -->
<script setup>
import { ref, provide, readonly } from 'vue'

const userInfo = ref({ name: 'admin', role: 'admin' })

//
Provide a read-only version of responsive data
provide('userInfo', readonly(userInfo))

//
Provide modification methods (subcomponents can only be modified through methods)
const updateUserName = (newName) => {
userInfo.value.name = newName
}
provide('updateUserName', updateUserName)
</script>
<!-- Sub component: User -->
<script setup>
import { inject } from 'vue'

const userInfo = inject('userInfo')
const updateUserName = inject('updateUserName')

//
Attempt to modify directly: Development environment warning (read-only)
const tryModify = () => {
userInfo.value.name = 'admin' //
Warning: Cannot modify read-only attribute
}

//
Correct modification: through the provided method
const modifyName = () => {
updateUserName('admin') //
Success: The modification has taken effect
}
</script>
Tips for avoiding pitfalls
  •  provide and inject key must be identical (for string types, it is recommended to use Symbol to avoid conflicts).
  • When passing reactive data, modifications to the child component will be synchronized to the parent component (if read-only is required, use the appropriate method readonly).
  • Do not overuse: Use + only for cross-level communication; for communication between parent and child components, use props+ preferentially emit.
  • It is recommended to manage it centrally in the top-level component provide to avoid it being scattered in multiple components (which makes it difficult to maintain).

8. Component Tool API: useAttrs /useSlots

<script setup> The `attrs` property of a component is obtained in the `<div>` tag, and the `slots` property is obtained in the `<div>` tag, which is used to develop general UI components (such as buttons, cards, and form components) .

1. useAttrs: Gets non-prop attributes of a component.

Usage example
<!-- Sub component: MyButton. vue -->
<script setup>
import { useAttrs } from 'vue'

//
Retrieve all non props attributes (such as type, class, style, event listener, etc.)
const attrs = useAttrs()

//
Accessing a single attribute
console.log(attrs.type) //
Passing type="primary" as the parent component
console.log(attrs.onClick) //
@click event passed by parent component
</script>

<template>
<!--
Transparent transmission of all attrs (v-bind="attrs") -->
<button v-bind="attrs" class="my-button">
<slot /> <!--
Render default slot -->
</button>
</template>

<style scoped>
.my-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
}
/*
Customize styles with attrs.type */
.my-button[type="primary"] {
background: #42b983;
color: #fff;
}
.my-button[type="danger"] {
background: #f56c6c;
color: #fff;
}
</style>
<!-- Parent component: Use MyButton -->
<template>
<MyButton type="primary" @click="handleClick" class="custom-class">
Main buttons
</MyButton>
<MyButton type="danger" @click="handleDelete">
Danger button
</MyButton>
</template>

<script setup>
const handleClick = () => console.log('
Click main button')
const handleDelete = () => console.log('
Click the danger button')
</script>
Key Explanation
  • attrs Includes: non-props properties, native event listeners (such as onClick), class and style;
  • When passing data through attrsv-bind="attrs" attributes and events are automatically bound to the element;
  • To exclude certain attributes, you can manually destructure them attrs:const { type, ...restAttrs } = attrs

2. useSlots: Get the slots of the component.

Usage example
<!-- Sub component: MyCard.vue -->
<script setup>
import { useSlots } from 'vue'

//
Get all slots
const slots = useSlots()

//
Check if there is a slot present
console.log(slots.default) //
Default slot (function if it exists)
console.log(slots.header) //
Named slot header (function if present)
console.log(slots.footer) //
Named slot footer (undefined if not present)
</script>

<template>
<div class="card">
<!--
Render named slot header (if exists) -->
<div class="card-header" v-if="slots.header">
<slot name="header" />
</div>
<!--
Render default Slot -->
<div class="card-body">
<slot />
</div>
<!--
Render named slot footer(if exists) -->
<div class="card-footer" v-if="slots.footer">
<slot name="footer" />
</div>
</div>
</template>

<style scoped>
.card {
border: 1px solid #eee;
border-radius: 8px;
padding: 16px;
}
.card-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 8px;
}
.card-footer {
margin-top: 16px;
color: #666;
}
</style>
<!-- Parent Component: Using MyCard -->
<template>
<MyCard>
<!--
Named slot header -->
<template #header>
<h3>
Card Title</h3>
</template>
<!--
Default Slot -->
<p>
Card content: This is a universal card component based on slots</p>
<!--
Named Slot Footer -->
<template #footer>
<button @click="handleClose">
Close</button>
</template>
</MyCard>
</template>

<script setup>
const handleClose = () => console.log('
Close Card')
</script>
Applicable Scenarios
  • Develop a general-purpose UI component library (such as buttons, cards, forms, and dialog boxes);
  • Components that support custom content (slots) and custom attributes/events (attrs) are required;
  • Component pass-through scenarios (such as passing the parent component’s properties/events to the child component’s internal elements).

IX. Commonly Used Lifecycle Hooks (Combined API Version)

The lifecycle hooks of the composable API need to be “imported on demand,” and their names begin with onthe following: The correspondence with Vue 2 is as follows:

Vue2 Option APIVue3 Composite APIeffectApplicable Scenarios
beforeCreate– (Execute directly in setup)Before component creationNone (setup is executed at the same time as beforeCreate + created)
created– (Execute directly in setup)After the component is createdInitialize data and request APIs (no need to wait for the DOM).
beforeMountonBeforeMountBefore component mountingPrepare for DOM-related operations (such as setting initial DOM properties).
mountedonMountedAfter the component is mountedDOM manipulation, initializing third-party libraries (such as charts and maps)
beforeUpdateonBeforeUpdateBefore component updateSave the DOM state before updates (such as scroll position).
updatedonUpdatedAfter component updateSynchronize the status of third-party libraries (such as updating chart data).
beforeUnmountonBeforeUnmountBefore component uninstallationClean up resources (timers, event listeners, API requests).
unmountedonUnmountedAfter component uninstallationFinal cleanup (such as destroying third-party library instances)
errorCapturedonErrorCapturedCatching child component errorsGlobal error handling and error log reporting

Usage example

<script setup>
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'

const count = ref(0)
let timer = null

//
Execute after component mounting (DOM rendered)
onMounted(() => {
console.log('
Component mounted complete:', document.getElementById('count').innerText)
//
Initialize timer
timer = setInterval(() => {
count.value++
}, 1000)
})

//
Execute after component update
onUpdated(() => {
console.log('
Component update completed:', count.value)
})

//
Execute before component uninstallation (clean up resources)
onUnmounted(() => {
console.log('
Component is about to be uninstalled')
clearInterval(timer) //
Clear timer
//
Cancel interface requests, unbind event monitoring, etc
})
</script>

<template>
<div id="count">{{ count }}</div>
</template>

Summary: A Review of Key Points of Commonly Used Composite APIs

1. Responsive Design Basics

  • ref Basic type + compatible with complex types, requires .value support for replacing the entire object;
  • reactive Only for complex types; no need to replace .value; does not support replacing the entire object.
  • Selection principles: Use basic types ref, use simple objects reactive, use those that require destructuring/replacement ref.

2. Responsive Derivation and Listening

  • computed Cache derived values, support read-only/write, and avoid side effects;
  • watch Precise monitoring, supporting old values/multi-source/deep monitoring, suitable for scenarios requiring explicit control;
  • watchEffect Automatic dependency collection is simple, efficient, and suitable for scenarios that do not require old values.
  • watchPostEffect/watchSyncEffect: Controls the timing of execution and simplifies configuration.

3. Auxiliary and Tool APIs

  • toReftoRefs: Solve reactive the problem of losing responsiveness in deconstruction;
  • toRaw: Retrieve original values ​​and optimize batch modification;
  • readonlyshallowReadonly: Protects data from being tampered with;
  • shallowRefshallowReactive: Optimizes performance for deep data.

4. Component Communication and Tools

  • provide/inject: Cross-level communication, working together readonly to ensure data security;
  • useAttrs/useSlots: Develop general-purpose UI components that support attribute pass-through and slot customization.

5. Lifecycle

  • Core hooks: onMounted (DOM manipulation), onUnmounted (resource cleanup), onUpdated (state synchronization);
  • The setup is executed when the command is called: it is equivalent to beforeCreate the + command created and no additional hooks are needed.

Learning suggestions

  1. First, master the core API: ref/reactive → computed → watch/watchEffect → lifecycle;
  2. Further learning of auxiliary APIs: toRefs/toRaw → readonly → shallow series;
  3. Final practical scenario: provide/inject → useAttrs/useSlots;
  4. Write more examples (such as TodoList, form linkage, and general components) to understand the purpose of the API in conjunction with real-world scenarios.