阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 订阅state变化

订阅state变化

作者:陈川 阅读数:9280人阅读 分类: Vue.js

订阅state变化

Vue.js的核心特性之一是响应式系统,它允许开发者声明式地描述UI与数据之间的关系。当应用状态发生变化时,视图会自动更新。理解如何订阅这些变化对于构建复杂应用至关重要。

响应式基础

Vue使用Object.defineProperty(Vue 2)或Proxy(Vue 3)来实现响应式。当组件实例被创建时,Vue会递归地将data对象的所有属性转换为getter/setter:

// Vue 2响应式原理简化示例
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log(`获取${key}: ${val}`)
      return val
    },
    set(newVal) {
      if (newVal !== val) {
        console.log(`设置${key}: ${newVal}`)
        val = newVal
      }
    }
  })
}

watch选项

组件选项中的watch允许观察特定数据源并在变化时执行回调:

export default {
  data() {
    return {
      count: 0,
      user: {
        name: 'Alice',
        age: 25
      }
    }
  },
  watch: {
    // 简单路径
    count(newVal, oldVal) {
      console.log(`count从${oldVal}变为${newVal}`)
    },
    // 深度观察
    user: {
      handler(newVal) {
        console.log('用户信息变化:', newVal)
      },
      deep: true,
      immediate: true
    },
    // 观察嵌套属性
    'user.name'(newName) {
      console.log('用户名变为:', newName)
    }
  }
}

$watch方法

除了选项式API,还可以在组件实例上使用$watch方法:

created() {
  this.$watch(
    () => this.count,
    (newVal, oldVal) => {
      console.log('$watch监听到count变化:', oldVal, '->', newVal)
    },
    { immediate: true }
  )
  
  // 观察计算属性
  this.$watch(
    () => this.count * 2,
    (newVal) => {
      console.log('count的两倍现在是:', newVal)
    }
  )
}

计算属性

计算属性是基于它们的响应式依赖进行缓存的:

computed: {
  formattedUser() {
    return `${this.user.name} (${this.user.age}岁)`
  },
  // 带setter的计算属性
  fullName: {
    get() {
      return `${this.firstName} ${this.lastName}`
    },
    set(newValue) {
      const names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

副作用与watchEffect

Vue 3引入了watchEffect,自动追踪其依赖:

import { watchEffect, ref } from 'vue'

setup() {
  const count = ref(0)
  
  watchEffect(() => {
    console.log('count值:', count.value)
    // 自动追踪count.value作为依赖
  })
  
  // 停止观察
  const stop = watchEffect(() => {...})
  stop()
  
  return { count }
}

自定义响应式逻辑

使用reactiveref创建响应式对象:

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 }
  }
}

响应式工具函数

Vue提供了一些响应式工具函数:

import { isRef, unref, toRef, toRefs } from 'vue'

setup(props) {
  // 解构props保持响应性
  const { title } = toRefs(props)
  
  // 创建对props.count的ref
  const countRef = toRef(props, 'count')
  
  function logValue(maybeRef) {
    // 如果是ref则返回.value,否则返回原值
    console.log(unref(maybeRef))
  }
  
  return { title, countRef, logValue }
}

性能优化

过度订阅state变化可能导致性能问题:

watch(
  () => state.items,
  () => {
    // 避免在items数组内容变化时频繁触发
  },
  { deep: true, flush: 'post' }
)

// 使用markRaw跳过响应式转换
import { markRaw } from 'vue'
const heavyObject = markRaw({ ... })

与第三方状态管理集成

订阅Vuex或Pinia的状态变化:

// Vuex示例
computed: {
  ...mapState(['count'])
},
watch: {
  count(newVal) {
    console.log('Vuex count变化:', newVal)
  }
}

// Pinia示例
import { useStore } from '@/stores/counter'
setup() {
  const store = useStore()
  
  store.$subscribe((mutation, state) => {
    console.log('存储变化:', mutation.type, state)
  })
  
  return {}
}

调试响应式依赖

Vue 3提供了调试钩子:

import { onRenderTracked, onRenderTriggered } from 'vue'

setup() {
  onRenderTracked((event) => {
    console.log('依赖被追踪:', event)
  })
  
  onRenderTriggered((event) => {
    console.log('依赖触发更新:', event)
  })
}

响应式模式实践

构建自动保存功能:

// 自动保存表单
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 }
)

响应式边界

理解响应式系统的限制:

// 数组变更检测注意事项
const list = reactive(['a', 'b'])
// 这些能触发响应
list.push('c')
list.splice(0, 1)
// 这些不能
list[0] = 'x' // Vue 2
list.length = 0 // Vue 2

// 动态添加属性
const obj = reactive({})
// Vue 2需要Vue.set
Vue.set(obj, 'newProp', 123)
// Vue 3可以直接赋值
obj.newProp = 123

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌