阿里云主机折上折
  • 微信号
Current Site:Index > Subscribe to state changes

Subscribe to state changes

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

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

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 ☕.