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 ”.valueobject 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 refCreate a wrapper object with a. value attribute
function ref(initialValue) {
//Create a proxy for the packaged object to intercept the reading and writing of. value
const wrapper = {
value: initialValue
}
//Dependency collection
return new Proxy(wrapper, {
get(target, key) {
if (key === 'value') {
track(target, 'get', key) //Trigger update
return target[key]
}
},
set(target, key, newValue) {
if (key === 'value') {
target[key] = newValue
trigger(target, 'set', key) //
return true
}
}
})
}
Example of layered usage
(1) Basic usage: basic types
<script setup>Define basic types of responsive data
import { ref } from 'vue'
// 1.Access/modification:. value must be added to the logic
const username = ref('admin') // String
const age = ref(18) // Number
const isStudent = ref(true) // Boolean
const emptyVal = ref(null) // null
// 2.Triggers responsive update
const updateUser = () => {
username.value = 'admin' //Switch states
age.value += 2 // 18 → 20
isStudent.value = false //emplate: No need for. value (Vue automatically parses wrapper objects)
}
// 3.Modify Information
</script>
<template>
<p>username:{{ username }}</p>
<p>age:{{ age }}</p>
<button @click="updateUser"></button>
</template>
(2) Advanced usage: Complex types (objects/arrays)
<script setup>Define complex types (automatically converted to reactive internally)
import { ref } from 'vue'
//Basketball
const user = ref({
name: 'admin',
address: {
city: 'new york'
}
})
const hobbyList = ref(['', 'Game'])Modify complex types: first obtain the reactive object through. value, and then manipulate its properties
//Modify properties directly (responsive)
const updateUserInfo = () => {
user.value.name = 'admin' //Deep attributes are also supported
user.value.address.city = 'new york' //travel
hobbyList.value.push('') //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 usedage.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 typeRef<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 reactiveOnly effective for objects/arrays, basic types return directly
function reactive(target) {
//Create a proxy agent to intercept attribute read and write operations
if (typeof target !== 'object' || target === null) {
return target
}
//Dependency collection
return new Proxy(target, {
get(target, key) {
track(target, 'get', key) //Deep object recursive proxy (such as user. address. city)
//Set properties
const result = Reflect.get(target, key)
return typeof result === 'object' ? reactive(result) : result
},
set(target, key, newValue) {
Reflect.set(target, key, newValue) //Trigger update
trigger(target, 'set', key) //
return true
}
})
}
Example of layered usage
(1) Basic usage: object
<script setup>Define object type responsive data
import { reactive } from 'vue'
//Modify data: Directly manipulate attributes (without the need for. value)
const user = reactive({
name: 'admin',
age: 18,
isStudent: true
})
//
const updateUser = () => {
user.name = 'admin'
user.age = 20
}
</script>
(2) Advanced usage: array + deep objects
<script setup>Array type
import { reactive } from 'vue'
//Learn Vue3
const todoList = reactive([
{ id: 1, text: '', done: false },Mastering Composite APIs
{ id: 2, text: '', done: false }Deep objects
])
//350 5th Avenue
const company = reactive({
name: 'Vue',
address: {
city: 'new york',
detail: '''Front end ',' Back end ',' Product '
},
departments: []Array operation (responsive)
})
//Practical Project
const addTodo = () => {
todoList.push({ id: 3, text: '', done: false })Deep Object Modification (Responsive)
}
const toggleTodo = (id) => {
const todo = todoList.find(item => item.id === id)
todo.done = !todo.done
}
//Design
const updateAddress = () => {
company.address.city = 'new york'
company.departments.push('')
}
</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 Vue2Vue.set, not required in Vue3).
4. The Ultimate Guide to Choosing Between Ref and Reactive
| Scene | Preferred API | reason |
|---|---|---|
| Primitive types (String/Number/Boolean) | ref | reactive does not support primitive types |
| Simple objects (few properties, no need for destructuring) | reactive | No need for .value, concise code |
| Complex objects (requiring destructuring or replacement of the entire object) | ref | Deconstruction without losing responsiveness, supports direct replacement |
| TypeScript Development | ref | Type inference is more user-friendly (e.g. Ref<number>) |
| Array type | Both 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:
- Upon first access to
computeda value, the computed function is executed, and dependencies are collected (e.g.count); - When the dependent data changes, it is marked
computedas “dirty” and recalculated on the next access; - 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>Raw responsive data
import { ref, computed } from 'vue'
//Read only calculation attribute: dependent on count/price/discount
const count = ref(0)
const price = ref(100)
const discount = ref(0.8)
//ntCalculate total price (executed only when dependent on changes)
const totalPrice = computed(() => {
console.log('')Multiple visits to total price, execute calculation function only for the first time/when
return (count.value * price.value * discount.value).toFixed(2)
})
//When the dependency remains unchanged, directly return the cached value
const logTotal = () => {
console.log(totalPrice.value) //Do not execute calculation function
console.log(totalPrice.value) //Quantity
}
</script>
<template>
<p>:{{ count }}</p>Unit price
<p>:{{ price }}</p>Discount
<p>:{{ discount }}</p>Total Price
<p>:{{ totalPrice }}</p> <!--Directly used in the template, no need for .value-->Increase quantity
<button @click="count++"></button>Print Total Price
<button @click="logTotal"></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>Original data: surname+given name
import { ref, computed } from 'vue'
//Michael
const firstName = ref('')Write calculation attribute: Full name (get, set modify)
const lastName = ref('Smith')
//Get: Generate full name based on firstName/lastName
const fullName = computed({
//Set: When modifying the full name, split it in reverse to firstName/lastName
get() {
return `${firstName.value}${lastName.value}`
},
//Assuming the input format is "Name" (2 characters)
set(newValue) {
//Modify calculation properties and trigger the set method
if (newValue.length === 2) {
firstName.value = newValue[0]
lastName.value = newValue[1]
}
}
})
//Michael Smith
const updateFullName = () => {
fullName.value = '' //will trigger set, firstName='Michael ', lastName='Smith'input full name
}
</script>
<template>
<p>full name:{{ fullName }}</p>
<p>first name:{{ firstName }}</p>
<p>last name:{{ lastName }}</p>
<input v-model="fullName" placeholder=""> <!--binding-->Change to Michael Smith
<button @click="updateFullName"></button>
</template>
(3) Practical scenario: Filtering + sorting list
<script setup>Raw data: list+filtering criteria
import { ref, computed } from 'vue'
//Frontend
const list = ref([
{ name: 'Vue3', type: '', score: 95 },Backend
{ name: 'React', type: 'Frontend', score: 90 },
{ name: 'Node.js', type: '', score: 88 },Backend
{ name: 'Python', type: '', score: 92 }Filter type: all/frontend/backend
])
const filterType = ref('all') //Sort field: score/name
const sortBy = ref('score') //Calculation attribute: filtered+sorted list
//filter
const filteredList = computed(() => {
// 1.sort
let result = list.value.filter(item => {
return filterType.value === 'all' ? true : item.type === filterType.value
})
// 2.Score in descending order
result.sort((a, b) => {
if (sortBy.value === 'score') {
return b.score - a.score //Name in ascending order
} else {
return a.name.localeCompare(b.name) //All
}
})
return result
})
</script>
<template>
<div>
<select v-model="filterType">
<option value="all"></option>Frontend
<option value="Frontend"></option>Backend
<option value="">Backend</option>Sort by score
</select>
<select v-model="sortBy">
<option value="score"></option>Sort by name
<option value="name"></option>Score
</select>
<ul>
<li v-for="item in filteredList" :key="item.name">
{{ item.name }}({{ item.type }})-:{{ 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)
| API | Listening source specified | Timing of Execution | Old value acquisition | Dependency collection | Applicable Scenarios |
|---|---|---|---|---|---|
| watch | Manually specify (e.g., count, ()=>user.name) | Execute synchronously (default) | Support (newVal, oldVal) | Listen only to specified sources | Requires old values and precise monitoring of single/multiple sources. |
| watchEffect | Automatically collect reactive data used in callbacks. | Execute synchronously (default) | Not supported | Automatic dependency collection | No need for old values, listen to multiple scattered sources |
| watchPostEffect | Automatic collection | Execute after DOM update | Not supported | Automatic dependency collection | Execution logic needs to be based on the updated DOM. |
| watchSyncEffect | Automatic collection | Synchronous execution (forced) | Not supported | Automatic dependency collection | This 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>Monitor individual ref data
import { ref, watch } from 'vue'
const count = ref(0)
//count changes from
watch(count, (newVal, oldVal) => {
console.log(`${oldVal} to ${newVal}`)Execute immediately upon component mounting (default false)
}, {
immediate: true, //Basic types do not require deep listening
deep: false //
})
</script>
(2) Advanced usage 1: Listening to reactive objects (deep listening)
<script setup>350 5th Avenue
import { reactive, watch } from 'vue'
const user = reactive({
name: 'admin',
address: {
city: 'new york',
area: ''Monitor the entire reactive object (automatic deep monitoring, can omit deep: true)
}
})
//Note: newUser and oldUser are the same object (because Proxy proxies the original object)
watch(user, (newUser, oldUser) => {
//user changed
console.log(':', newUser.address.city)The listener must enable deep listening (default is true)
}, {
immediate: false,
deep: true //Accurately monitor individual attributes of objects (with better performance and no need for deep monitoring)
})
//Listening function (returns the properties to be listened to)
watch(
() => user.address.city, //City changes from
(newCity, oldCity) => {
console.log(`${oldCity} to ${newCity}`)
}
)
</script>
(3) Advanced usage 2: Monitoring multiple data sources
<script setup>Listening to multiple sources: array format
import { ref, reactive, watch } from 'vue'
const count = ref(0)
const user = reactive({ name: 'admin' })
//First source
watch(
[count, () => user.name], //: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 executedundefined.
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>Automatically collect dependencies: count and user.name
import { ref, reactive, watchEffect } from 'vue'
const count = ref(0)
const user = reactive({ name: 'admin' })
//Modifying any dependency will trigger a callback
watchEffect(() => {
console.log(`count: ${count.value},name: ${user.name}`)
})
//triggers callback
const updateData = () => {
count.value++ //will also trigger a callback
// user.name = 'admin2' //
}
</script>
(2) Advanced usage 1: Stop listening + clean up side effects
<script setup>WatchEffect returns the stop function
import { ref, watchEffect } from 'vue'
const inputVal = ref('')
//Simulate interface requests (side effects)
const stopWatch = watchEffect((onInvalidate) => {
//search
const timer = setTimeout(() => {
console.log(':', inputVal.value)Cleanup function: executed during dependency changes/component uninstallation (to avoid duplicate requests)
}, 500)
//Manually stop monitoring
onInvalidate(() => {
clearTimeout(timer)
})
})
//input search content
const stop = () => {
stopWatch()
}
</script>
<template>
<input v-model="inputVal" placeholder="">Stop listening
<button @click="stop"></button>
</template>
(3) Advanced usage 2: Execution timing configuration (flush)
<script setup>Default (flush:'sync'): Synchronized execution (triggered immediately by data changes)
import { ref, watchEffect } from 'vue'
const count = ref(0)
// 1.ynchronous Execution
watchEffect(() => {
console.log(':', count.value)Flush: 'post': Execute after DOM update (equivalent to watchPostEffect)
})
// 2.Can obtain updated DOM nodes
watchEffect(() => {
//Execute after DOM update
console.log(':', document.getElementById('count').innerText)Flush: 'pre': Execute before component update (rarely used)
}, { flush: 'post' })
// 3.Execute before component update
watchEffect(() => {
console.log(':', 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 mandatoryonInvalidate(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>Execute after DOM update
import { ref, watchPostEffect, watchSyncEffect } from 'vue'
const count = ref(0)
//DOM updated
watchPostEffect(() => {
console.log(':', document.getElementById('count').innerText)Synchronous execution
})
//Synchronous Execution
watchSyncEffect(() => {
console.log(':', 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>Create a responsive reference for user.name
import { reactive, toRef } from 'vue'
const user = reactive({
name: 'admin',
age: 18
})
//Modify nameRef and update the original object synchronously
const nameRef = toRef(user, 'name')
//Output
nameRef.value = 'admin2'
console.log(user.name) //:admin2Modify 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>/Batch conversion to ref objects
import { reactive, toRefs } from 'vue'
const user = reactive({
name: 'admin',
age: 18,
address: { city: 'new york' }
})
//structure
const userRefs = toRefs(user)
// userRefs:{ name: Ref('admin'), age: Ref(18), address: Ref(...) }Maintain responsiveness even after deconstruction
//Add .value when modifying
const { name, age, address } = userRefs
//synchronized update
name.value = 'admin2' // user.namesynchronized update
address.value.city = 'new york' // user.address.city
</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 manuallytoRef). - 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>Handling reactive objects
import { reactive, ref, toRaw } from 'vue'
// 1.Get the original object
const user = reactive({ name: 'admin' })
const rawUser = toRaw(user) //Modifying original values: will not trigger responsive updates
//Output
rawUser.name = 'admin2'
console.log(user.name) //:admin2(the original object will also change, but there is no responsive trigger)Processing ref objects (requires accessing .value first)
// 2.ref needs to first take.
const count = ref(0)
const rawCount = toRaw(count.value) //.valueNo response, view not updated
rawCount = 10 //Practical scenario: Batch modification of data (to avoid multiple triggering of updates)
// 3.Batch modify the original array, triggering only one update (if directly modifying the list, it will trigger multiple updates)
const list = reactive([1, 2, 3, 4, 5])
const rawList = toRaw(list)
//
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>Equivalent to: const val1=isRef (count)? count.value : count
import { ref, unref } from 'vue'
const count = ref(0)
const num = 10
//Output
const val1 = unref(count) //:0Equivalent to: const val2=isRef (num)? num.value : num
//Output
const val2 = unref(num) //:10Practical 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>350 5th Avenue
import { reactive, readonly, shallowReadonly } from 'vue'
const user = reactive({
name: 'admin',
address: {
city: 'new york',
area: ''Deep read-only: All attributes at all levels cannot be modified
}
})
// 1.Development environment warning: Cannot modify read-only property
const readOnlyUser = readonly(user)
readOnlyUser.name = 'admin' //Development environment warning: deep properties cannot be modified either
readOnlyUser.address.city = 'new york' //Shallow read-only: Only top-level attributes cannot be modified, while deep level attributes can be modified
// 2.Development environment warning: top-level properties cannot be modified
const shallowUser = shallowReadonly(user)
shallowUser.name = 'admin' //Success: Deep attributes can be modified (no warning)
shallowUser.address.city = 'new york' //)Read only ref object (directly wrap ref with readonly)
// 3.Development environment warning
const count = ref(0)
const readOnlyCount = readonly(count)
readOnlyCount.value = 10 //
</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)
| API | Listening layer | Triggering update conditions | Applicable Scenarios |
|---|---|---|---|
| ref | deep | Attribute changes at any level | Basic types and complex types that need to monitor deep changes. |
| shallowRef | shallow | .valueWhen replacing only | Deep data structures only require replacing the entire object. |
| reactive | deep | Attribute changes at any level | Simple objects that need to listen for deep changes |
| shallowReactive | shallow | Only top-level attributes change | For 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>Shallow ref: only listens for substitution of. value
import { shallowRef } from 'vue'
//Trigger responsive update (replace the entire .value)
const user = shallowRef({
name: 'admin',
address: { city: 'new york' }
})
//Do not trigger responsive updates (modify deep attributes)
user.value = { name: 'admin', address: { city: 'new york' } }
//Manually trigger updates (if monitoring for deep changes)
user.value.address.city = 'new york'
//Manually trigger responsive updates
import { triggerRef } from 'vue'
const updateDeep = () => {
user.value.address.city = 'new york'
triggerRef(user) //
}
</script>
2. shallowReactive: Only listens to the top-level property.
<script setup>Shallow reactive: only listens to top-level properties
import { shallowReactive } from 'vue'
//Trigger responsive update (modify top-level properties)
const list = shallowReactive([
{ id: 1, name: 'Vue3', detail: { score: 95 } },
{ id: 2, name: 'React', detail: { score: 90 } }
])
//Do not trigger responsive updates (modify deep attributes)
list[0].name = 'Vue3.4'
list.push({ id: 3, name: 'Node.js' })
//Manually trigger updates (if monitoring for deep changes)
list[0].detail.score = 98
//Manually triggered
import { triggerReactivity } from 'vue'
const updateDeep = () => {
list[0].detail.score = 98
triggerReactivity(list[0].detail) //
}
</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-->Inject data (the second parameter is the default value)
<script setup>
import { inject } from 'vue'
//default name
const appName = inject('appName', '')unknown
const version = inject('version', '1.0.0')
const author = inject('author', '') //No provider, use default valueApplication Name
</script>
<template>
<p>:{{ appName }}</p>Version
<p>:{{ version }}</p>Author
<p>:{{ author }}</p>
</template>
(2) Advanced usage: Passing reactive data + methods
<!--Top level component: Provider-->Responsive data
<script setup>
import { ref, provide } from 'vue'
//Theme
const theme = ref('light') //:light/darkMethod: Modify the theme
//Provide responsive data and methods
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
//
provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script>
<!--Deep Sub Component: User-->Injecting responsive data and methods
<script setup>
import { inject } from 'vue'
//Current theme
const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>
<template>
<div :class="`app theme-${theme.value}`">
<p>:{{ theme.value }}</p>Switch Theme
<button @click="toggleTheme"></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-->Provide a read-only version of responsive data
<script setup>
import { ref, provide, readonly } from 'vue'
const userInfo = ref({ name: 'admin', role: 'admin' })
//Provide modification methods (subcomponents can only be modified through methods)
provide('userInfo', readonly(userInfo))
//
const updateUserName = (newName) => {
userInfo.value.name = newName
}
provide('updateUserName', updateUserName)
</script>
<!--Sub component: User-->Attempt to modify directly: Development environment warning (read-only)
<script setup>
import { inject } from 'vue'
const userInfo = inject('userInfo')
const updateUserName = inject('updateUserName')
//Warning: Cannot modify read-only attribute
const tryModify = () => {
userInfo.value.name = 'admin' //Correct modification: through the provided method
}
//Success: The modification has taken effect
const modifyName = () => {
updateUserName('admin') //
}
</script>
Tips for avoiding pitfalls
-
provideandinjectkeymust 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+ preferentiallyemit. - It is recommended to manage it centrally in the top-level component
provideto 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-->Retrieve all non props attributes (such as type, class, style, event listener, etc.)
<script setup>
import { useAttrs } from 'vue'
//Accessing a single attribute
const attrs = useAttrs()
//Passing type="primary" as the parent component
console.log(attrs.type) //@click event passed by parent component
console.log(attrs.onClick) //Transparent transmission of all attrs (v-bind="attrs")
</script>
<template>
<!---->Render default slot
<button v-bind="attrs" class="my-button">
<slot /> <!---->Customize styles with attrs.type
</button>
</template>
<style scoped>
.my-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
}
/**/
.my-button[type="primary"] {
background: #42b983;
color: #fff;
}
.my-button[type="danger"] {
background: #f56c6c;
color: #fff;
}
</style>
<!--Parent component: Use MyButton-->Main buttons
<template>
<MyButton type="primary" @click="handleClick" class="custom-class">
Danger button
</MyButton>
<MyButton type="danger" @click="handleDelete">
Click main button
</MyButton>
</template>
<script setup>
const handleClick = () => console.log('')Click the danger button
const handleDelete = () => console.log('')
</script>
Key Explanation
attrsIncludes: non-props properties, native event listeners (such asonClick),classandstyle;- When passing data through
attrs,v-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-->Get all slots
<script setup>
import { useSlots } from 'vue'
//Check if there is a slot present
const slots = useSlots()
//Default slot (function if it exists)
console.log(slots.default) //Named slot header (function if present)
console.log(slots.header) //Named slot footer (undefined if not present)
console.log(slots.footer) //Render named slot header (if exists)
</script>
<template>
<div class="card">
<!---->Render default Slot
<div class="card-header" v-if="slots.header">
<slot name="header" />
</div>
<!---->Render named slot footer
<div class="card-body">
<slot />
</div>
<!--(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-->Named slot header
<template>
<MyCard>
<!---->Card Title
<template #header>
<h3></h3>Default Slot
</template>
<!---->Card content: This is a universal card component based on slots
<p></p>Named Slot Footer
<!---->Close
<template #footer>
<button @click="handleClose"></button>Close Card
</template>
</MyCard>
</template>
<script setup>
const handleClose = () => console.log('')
</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 API | Vue3 Composite API | effect | Applicable Scenarios |
|---|---|---|---|
| beforeCreate | – (Execute directly in setup) | Before component creation | None (setup is executed at the same time as beforeCreate + created) |
| created | – (Execute directly in setup) | After the component is created | Initialize data and request APIs (no need to wait for the DOM). |
| beforeMount | onBeforeMount | Before component mounting | Prepare for DOM-related operations (such as setting initial DOM properties). |
| mounted | onMounted | After the component is mounted | DOM manipulation, initializing third-party libraries (such as charts and maps) |
| beforeUpdate | onBeforeUpdate | Before component update | Save the DOM state before updates (such as scroll position). |
| updated | onUpdated | After component update | Synchronize the status of third-party libraries (such as updating chart data). |
| beforeUnmount | onBeforeUnmount | Before component uninstallation | Clean up resources (timers, event listeners, API requests). |
| unmounted | onUnmounted | After component uninstallation | Final cleanup (such as destroying third-party library instances) |
| errorCaptured | onErrorCaptured | Catching child component errors | Global error handling and error log reporting |
Usage example
<script setup>Execute after component mounting (DOM rendered)
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
const count = ref(0)
let timer = null
//Component mounted complete
onMounted(() => {
console.log(':', document.getElementById('count').innerText)Initialize timer
//Execute after component update
timer = setInterval(() => {
count.value++
}, 1000)
})
//Component update completed
onUpdated(() => {
console.log(':', count.value)Execute before component uninstallation (clean up resources)
})
//Component is about to be uninstalled
onUnmounted(() => {
console.log('')Clear timer
clearInterval(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
refBasic type + compatible with complex types, requires.valuesupport for replacing the entire object;reactiveOnly for complex types; no need to replace.value; does not support replacing the entire object.- Selection principles: Use basic types
ref, use simple objectsreactive, use those that require destructuring/replacementref.
2. Responsive Derivation and Listening
computedCache derived values, support read-only/write, and avoid side effects;watchPrecise monitoring, supporting old values/multi-source/deep monitoring, suitable for scenarios requiring explicit control;watchEffectAutomatic 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
toRef/toRefs: Solvereactivethe problem of losing responsiveness in deconstruction;toRaw: Retrieve original values and optimize batch modification;readonly/shallowReadonly: Protects data from being tampered with;shallowRef/shallowReactive: Optimizes performance for deep data.
4. Component Communication and Tools
provide/inject: Cross-level communication, working togetherreadonlyto 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
beforeCreatethe + commandcreatedand no additional hooks are needed.
Learning suggestions
- First, master the core API:
ref/reactive→computed→watch/watchEffect→ lifecycle; - Further learning of auxiliary APIs:
toRefs/toRaw→readonly→shallowseries; - Final practical scenario:
provide/inject→useAttrs/useSlots; - Write more examples (such as TodoList, form linkage, and general components) to understand the purpose of the API in conjunction with real-world scenarios.