Shallow reactive (shallowReactive/shallowRef)
Concept of Shallow Reactivity (shallowReactive/shallowRef)
Vue3's reactivity system provides two shallow reactivity APIs: shallowReactive
and shallowRef
. They are similar to the standard reactive
and ref
but only track reactivity for the first-level properties of an object or primitive values. This design can significantly improve performance in specific scenarios, especially when dealing with large objects or data structures that don't require deep reactivity.
import { reactive, shallowReactive, ref, shallowRef } from 'vue'
// Standard reactivity
const deepObj = reactive({
nested: { count: 0 }
})
// Shallow reactivity
const shallowObj = shallowReactive({
nested: { count: 0 }
})
How shallowReactive Works
shallowReactive
only creates reactive proxies for an object's direct properties, leaving nested objects unchanged. This means modifying nested properties won't trigger view updates:
const state = shallowReactive({
level1: {
level2: { value: 'test' }
}
})
// This will trigger reactivity updates
state.level1 = { newValue: 'changed' }
// This won't trigger reactivity updates
state.level1.level2.value = 'new value'
This feature is particularly useful when handling large configuration objects or objects returned by third-party libraries, as it avoids unnecessary reactivity overhead.
Unique Behavior of shallowRef
The main difference between shallowRef
and regular ref
is that it doesn't perform deep reactivity conversion on its .value
:
const deepRef = ref({ count: 0 })
const shallow = shallowRef({ count: 0 })
// Standard ref - will trigger updates
deepRef.value.count++
// shallowRef - won't trigger updates
shallow.value.count++
// shallowRef - only triggers updates when .value is replaced
shallow.value = { count: 1 }
Performance Optimization Scenarios
Shallow reactivity can provide noticeable performance improvements in the following scenarios:
- Large list rendering: When list items contain complex nested structures but only top-level changes need to be tracked
const bigList = shallowRef([
{ id: 1, data: { /* large nested data */ } },
// ...hundreds of similar objects
])
- Third-party library integration: When wrapping objects not managed by Vue
const thirdPartyObj = shallowReactive(someLibrary.getComplexConfig())
- Immutable data patterns: Used with Object.assign or spread operators
const state = shallowRef({ items: [] })
function update() {
state.value = {
...state.value,
items: [...state.value.items, newItem]
}
}
Usage with watch
Special attention is needed when using shallow reactivity with watch
:
const state = shallowReactive({ nested: { value: 0 } })
// By default, it won't deep watch
watch(() => state.nested.value, (newVal) => {
console.log('Won\'t trigger', newVal)
})
// Need to explicitly specify deep: true
watch(() => state.nested.value, (newVal) => {
console.log('Will trigger', newVal)
}, { deep: true })
Practical Application Example
Handling row data in a table component:
// Table component
setup() {
const rows = shallowRef([])
async function loadData() {
const data = await fetchLargeDataset()
rows.value = data.map(item => ({
...item,
metadata: JSON.parse(item.metadata)
}))
}
function updateRow(index, newData) {
const newRows = [...rows.value]
newRows[index] = { ...newRows[index], ...newData }
rows.value = newRows
}
return { rows, updateRow }
}
Relationship with Reactivity Transform
Vue3's Reactivity Transform syntax sugar can also work with shallow reactivity:
const $ = reactiveTransform()
const count = $(shallowRef(0))
const state = $(shallowReactive({ nested: {} }))
// Automatic .value unpacking
count++
// But nested properties remain non-reactive
state.nested.value = 123 // Won't trigger updates
Edge Case Handling
Special scenarios to note when using shallow reactivity:
- Array operations: Directly modifying array elements won't trigger updates
const list = shallowReactive([{ id: 1 }, { id: 2 }])
list[0].id = 3 // No reaction
list.length = 1 // Will trigger
- Destructuring assignment: Will lose reactivity
const state = shallowReactive({ x: 1, y: 2 })
const { x } = state // x is no longer reactive
- Template usage: Accessing nested properties in templates will show the latest value but won't trigger dependency collection
<div>{{ shallowObj.nested.value }}</div>
<!-- Manually modifying nested.value won't update the view -->
Type System Integration
When using TypeScript, shallow reactivity APIs maintain full type hints:
interface User {
id: number
profile: {
name: string
age: number
}
}
const user = shallowReactive<User>({
id: 1,
profile: { name: 'Alice', age: 30 }
})
// Type checking works normally
user.id = '2' // Type error
user.profile.name = 123 // Type error
Comparison with Vuex/Pinia
State management libraries typically use deep reactivity, but shallow reactivity can optimize certain modules:
// In Pinia store
export const useBigDataStore = defineStore('big', () => {
const dataset = shallowRef<BigDataType>(null)
function partialUpdate(path: string[], value: any) {
// Manually control update logic
}
return { dataset, partialUpdate }
})
Debugging Tips
Identifying shallow reactive objects in developer tools:
- Chrome Vue Devtools will show
[ShallowReactive]
or[ShallowRef]
labels - Console logs can identify them via the
__v_isShallow
flag - Use
markRaw
to explicitly mark non-reactive parts:
const rawData = { /* large data structure */ }
markRaw(rawData)
const state = shallowReactive({
config: rawData // Explicitly not tracked
})
Application in Composable Functions
When writing reusable composable functions, shallow reactivity provides more flexible options:
export function usePagination(initialOptions) {
const options = shallowReactive({
page: 1,
pageSize: 10,
...initialOptions
})
const data = shallowRef([])
async function load() {
data.value = await fetchData(options)
}
return {
options,
data,
load
}
}
Integration with v-model
Shallow reactivity can work with v-model, but value replacement strategies need attention:
<template>
<!-- For shallowRef, manual handling is needed -->
<input
:value="shallowValue.value"
@input="shallowValue.value = $event.target.value"
>
<!-- shallowReactive can be used directly -->
<input v-model="shallowState.field">
</template>
<script setup>
const shallowValue = shallowRef('')
const shallowState = shallowReactive({ field: '' })
</script>
Memory Management Considerations
Shallow reactivity can reduce memory overhead in Vue's reactivity system, especially in:
- Large temporary objects with short lifespans
- Component states that are frequently created and destroyed
- Complex data structures used as intermediate calculation results
function processData(input) {
// Use shallow reactivity for temporary large objects
const tempState = shallowReactive(transformData(input))
// ...processing logic
return extractResult(tempState) // Automatically recycled when done
}
Testing Strategies
Special attention is needed when testing code that uses shallow reactivity:
// Test example
test('shallowRef update', async () => {
const value = shallowRef({ count: 0 })
// Replacing the entire value will trigger
value.value = { count: 1 }
await nextTick()
expect(/* ... */)
// Modifying properties won't trigger
value.value.count++
await nextTick()
// Assertions here need special handling
})
Vue2 Compatibility Considerations
For libraries needing to support both Vue2 and Vue3, shallow reactivity can be provided via an adapter pattern:
function createShallowReactive(obj) {
return isVue3
? shallowReactive(obj)
: Object.defineProperties({}, /* manually implement shallow tracking */)
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn