阿里云主机折上折
  • 微信号
Current Site:Index > Shallow reactive (shallowReactive/shallowRef)

Shallow reactive (shallowReactive/shallowRef)

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

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:

  1. 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
])
  1. Third-party library integration: When wrapping objects not managed by Vue
const thirdPartyObj = shallowReactive(someLibrary.getComplexConfig())
  1. 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:

  1. 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
  1. Destructuring assignment: Will lose reactivity
const state = shallowReactive({ x: 1, y: 2 })
const { x } = state // x is no longer reactive
  1. 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:

  1. Chrome Vue Devtools will show [ShallowReactive] or [ShallowRef] labels
  2. Console logs can identify them via the __v_isShallow flag
  3. 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:

  1. Large temporary objects with short lifespans
  2. Component states that are frequently created and destroyed
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.