Subscribe to state changes
Subscribing to State Changes
One of Vue.js's core features is its reactivity system, which allows developers to declaratively describe the relationship between the UI and data. When the application state changes, the view updates automatically. Understanding how to subscribe to these changes is crucial for building complex applications.
Reactivity Basics
Vue uses Object.defineProperty
(Vue 2) or Proxy
(Vue 3) to achieve reactivity. When a component instance is created, Vue recursively converts all properties of the data
object into getters/setters:
// Simplified Vue 2 reactivity example
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`Getting ${key}: ${val}`)
return val
},
set(newVal) {
if (newVal !== val) {
console.log(`Setting ${key}: ${newVal}`)
val = newVal
}
}
})
}
Watch Option
The watch
option in component options allows observing specific data sources and executing callbacks when changes occur:
export default {
data() {
return {
count: 0,
user: {
name: 'Alice',
age: 25
}
}
},
watch: {
// Simple path
count(newVal, oldVal) {
console.log(`count changed from ${oldVal} to ${newVal}`)
},
// Deep watch
user: {
handler(newVal) {
console.log('User info changed:', newVal)
},
deep: true,
immediate: true
},
// Watching nested property
'user.name'(newName) {
console.log('Username changed to:', newName)
}
}
}
$watch Method
In addition to the options API, you can use the $watch
method on component instances:
created() {
this.$watch(
() => this.count,
(newVal, oldVal) => {
console.log('$watch detected count change:', oldVal, '->', newVal)
},
{ immediate: true }
)
// Watching computed property
this.$watch(
() => this.count * 2,
(newVal) => {
console.log('Double count is now:', newVal)
}
)
}
Computed Properties
Computed properties are cached based on their reactive dependencies:
computed: {
formattedUser() {
return `${this.user.name} (${this.user.age} years old)`
},
// Computed property with setter
fullName: {
get() {
return `${this.firstName} ${this.lastName}`
},
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
Side Effects and watchEffect
Vue 3 introduces watchEffect
, which automatically tracks its dependencies:
import { watchEffect, ref } from 'vue'
setup() {
const count = ref(0)
watchEffect(() => {
console.log('count value:', count.value)
// Automatically tracks count.value as a dependency
})
// Stop watching
const stop = watchEffect(() => {...})
stop()
return { count }
}
Custom Reactive Logic
Create reactive objects using reactive
and ref
:
import { reactive, ref, watch } from 'vue'
export default {
setup() {
const state = reactive({
items: [],
loading: false
})
const searchQuery = ref('')
watch(
searchQuery,
async (newQuery) => {
state.loading = true
state.items = await fetchItems(newQuery)
state.loading = false
},
{ debounce: 300 }
)
return { state, searchQuery }
}
}
Reactive Utility Functions
Vue provides several reactive utility functions:
import { isRef, unref, toRef, toRefs } from 'vue'
setup(props) {
// Destructure props while maintaining reactivity
const { title } = toRefs(props)
// Create a ref for props.count
const countRef = toRef(props, 'count')
function logValue(maybeRef) {
// Returns .value if it's a ref, otherwise returns the original value
console.log(unref(maybeRef))
}
return { title, countRef, logValue }
}
Performance Optimization
Over-subscribing to state changes can cause performance issues:
watch(
() => state.items,
() => {
// Avoid frequent triggers when the items array content changes
},
{ deep: true, flush: 'post' }
)
// Use markRaw to skip reactivity conversion
import { markRaw } from 'vue'
const heavyObject = markRaw({ ... })
Integration with Third-Party State Management
Subscribing to state changes in Vuex or Pinia:
// Vuex example
computed: {
...mapState(['count'])
},
watch: {
count(newVal) {
console.log('Vuex count changed:', newVal)
}
}
// Pinia example
import { useStore } from '@/stores/counter'
setup() {
const store = useStore()
store.$subscribe((mutation, state) => {
console.log('Store changed:', mutation.type, state)
})
return {}
}
Debugging Reactive Dependencies
Vue 3 provides debugging hooks:
import { onRenderTracked, onRenderTriggered } from 'vue'
setup() {
onRenderTracked((event) => {
console.log('Dependency tracked:', event)
})
onRenderTriggered((event) => {
console.log('Dependency triggered update:', event)
})
}
Reactive Pattern Practices
Building an auto-save feature:
// Auto-saving form
const form = reactive({
title: '',
content: '',
lastSaved: null
})
let saveTimer
watch(
() => ({ ...form }),
(newForm) => {
clearTimeout(saveTimer)
saveTimer = setTimeout(async () => {
await saveToServer(newForm)
form.lastSaved = new Date()
}, 1000)
},
{ deep: true }
)
Reactivity Boundaries
Understanding the limitations of the reactivity system:
// Array change detection considerations
const list = reactive(['a', 'b'])
// These will trigger reactivity
list.push('c')
list.splice(0, 1)
// These won't
list[0] = 'x' // Vue 2
list.length = 0 // Vue 2
// Dynamically adding properties
const obj = reactive({})
// Vue 2 requires Vue.set
Vue.set(obj, 'newProp', 123)
// Vue 3 allows direct assignment
obj.newProp = 123
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:与Vue DevTools集成
下一篇:热模块替换支持