上一篇:Vue2和Vue3子系统
Vue2和Vue3全面对比的第二项是响应式数据,这是框架的核心机制。Vue通过响应式系统实现数据变化时视图自动更新,开发者只需关注数据本身,无需手动操作DOM。
Vue2基于Object.defineProperty实现响应式,存在无法检测属性新增/删除、数组索引修改等限制,需要借助Vue.set()/Vue.delete()等辅助API。Vue3采用Proxy代理实现响应式,全面拦截对象操作,无上述限制,同时引入了ref()、reactive()、computed()、watch()等函数式API,配合Composition API实现了更灵活的响应式数据管理。
|
对比项
|
Vue 2 功能
|
Vue 2 示例
|
Vue 3 功能
|
Vue 3 示例
|
|
基本类型响应式
|
data 选项
|
data() { return { count: 0, message: "hello" } } // 使用:this.count |
ref()
注意:在 script 中用 .value,在 template 中不用;ref 虽支持所有类型,但 reactive 提供更简洁、高效的响应式对象操作,避免 .value 层级,适合复杂状态结构
|
const count = ref(0) const message = ref("hello") // JS 中使用 .value count.value++ // 模板中自动解包 // {{ count }} |
|
对象类型响应式 |
data 选项 |
data() { return { user: { name: "", age: 0 } } } // 使用:this.user.name |
reactive()
注意:永远不要对 reactive 对象进行解构,除非用 toRefs |
const user = reactive({ name: "", age: 0 }) // 直接访问,无需 .value user.name = "张三" |
|
数组响应式 |
data 选项 (索引修改有限制) |
data() { return { list: [1, 2, 3] } } // 非响应式 this.list[0] = 99 this.list.length = 0 // 用变异方法 this.list.push(4) this.list.splice(0, 1) |
ref() / reactive() (Proxy 全面拦截)
注意:强烈建议用 ref([])而不是reactive([])来存储数组 |
const list = ref([1, 2, 3]) // 全部响应式 list.value[0] = 99 list.value.length = 0 list.value.push(4) |
|
计算属性 |
computed 选项 |
// 只读 counted: { double() { return this.count * 2 } } // 可写 computed: { fullName: { get() { return this.first + this.last }, set(val) { this.first = val[0] } } } |
computed() 函数
注意:computed 返回的是 Ref 对象,需要.value访问 |
// 只读 const double = computed( () => count.value * 2 ) // 可写 const fullName = computed({ get: () => first.value + last.value, set: (val) => { first.value = val[0] } }) |
|
侦听属性 |
watch 选项 |
watch: { count(newVal, oldVal) { console.log(newVal) }, // 深度监听 user: { handler(val) { /* ... */ }, deep: true }, // 立即执行 name: { handler(val) { /* ... */ }, immediate: true } } |
watch() 函数
注意:watch 监听的是 Ref 对象,但回调函数中不需要.value |
// 基本用法 watch(count, (n, o) => { console.log(n) }) // 深度监听 watch(user, (v) => {}, { deep: true }) // 立即执行 watch(name, (v) => {}, { immediate: true }) |
|
监听多个数据源 |
多个 watch 处理器 |
watch: { firstName(val) { this.full = val + this.lastName }, lastName(val) { this.full = this.firstName + val } } |
watch() 传入数组 |
watch( [firstName, lastName], ([f, l]) => { fullName.value = f + l } ) |
|
自动收集依赖侦听 |
无 |
— |
watchEffect() |
watchEffect(() => { console.log(count.value) }) // 自动追踪内部 // 使用的响应式数据 |
|
解构保持响应式 |
解构会丢失响应式 |
// 解构后丢失响应式 const { name } = this.user // name 不再是响应式 |
toRefs() / toRef() |
// 解构仍保持响应式 const { name, age } = toRefs(user) // name, age 仍是 ref
// 单个属性 const name = toRef(user, "name") |
|
响应式工具函数 |
无 |
— |
isRef() unref() toValue() |
isRef(count) // true
// 自动解包 ref unref(count) // = count.value
// Vue 3.3+ toValue(getterOrRef) |
|
新增属性 |
Vue.set() this.$set() |
// 方式一 this.$set(obj, "newKey", value)
// 方式二 Vue.set(obj, "newKey", value)
// 直接赋值不触发更新 // this.obj.newKey = value // |
直接赋值 |
// Proxy 自动追踪,直接赋值 obj.newKey = "新增属性"
const obj = reactive({}) obj.newKey = "hello" //响应式 |
|
删除属性 |
Vue.delete() this.$delete() |
// 方式一 this.$delete(obj, "key")
// 方式二 Vue.delete(obj, "key")
// 直接 delete 不触发更新 // delete this.obj.key // |
直接 delete |
// Proxy 自动追踪 delete obj.key // 响应式
const obj = reactive({ name: "" }) delete obj.name |
|
主动更新DOM |
this.$nextTick() |
|
await nextTick() |
|
|
底层实现原理 |
Object.defineProperty() |
// 拦截属性的 getter/setter Object.defineProperty( obj, "key", { get() { /* 收集依赖 */ }, set(v) { /* 派发更新 */ } } ) // 限制: // - 无法检测新增/删除属性 // - 无法检测数组索引修改 // - 需要递归遍历所有属性 |
Proxy |
// 代理整个对象 new Proxy(target, { get(target, key) { /* track */ }, set(target, key, val) { /* trigger */ }, deleteProperty(target, key) {} }) // 优势: // - 可检测新增/删除属性 // - 可检测数组索引修改 // - 惰性监听,按需追踪 |
以上对比可以清晰地看出Vue3在响应式系统方面的重大改进:从底层原理上用Proxy替代Object.defineProperty解决了诸多限制,从API设计上引入了ref/reactive/computed/watch等函数式API,提供了更灵活、更强大的响应式数据管理能力,使得开发者可以更精细地控制响应式行为。