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,提供了更灵活、更强大的响应式数据管理能力,使得开发者可以更精细地控制响应式行为。