声明响应式状态的两种方法:ref 和 reactive
ref
逻辑中需要.value访问内部值
模板中直接使用
import {ref} from 'vue'
const count = ref(0)
const count1 = ref(0)
function increment (event) {
console.log(event.target)
count1.value++ // 逻辑中需要.value访问内部值
}
<button type="button" @click="count++">count is {{ count }}</button> // 模板中直接使用{{count}}
<button type="button" @click="increment">count1 is {{ count1 }}</button>
reactive
import {reactive} from 'vue'
const user = reactive({
name: 'jack'
})
console.log(user.name)
ref和reactive底层实现的伪代码
// ref 的简化实现
function myRef(value) {
return {
get value() {
// 收集依赖
return value
},
set value(newVal) {
// 触发更新
value = newVal
}
}
}
// reactive 的简化实现
function myReactive(obj) {
return new Proxy(obj, {
get(target, key) {
// 收集依赖
return target[key]
},
set(target, key, value) {
// 触发更新
target[key] = value
return true
}
})
}
ref和reactive的区别
ref和reactive都是vue3创建响应式数据的API,但设计理念和使用场景不同:
- ref主要用于包装原始值(字符串、数字、布尔等)(但其实像对象、map任何类型的数据都可以),而reactive主要用于处理对象和数组和Map、Set集合
- ref需要通过.value的方式访问内部值,reactive则可以直接访问属性
- 实现原理:ref内部是用reactive包装的{value: xxx}对象,而reactive是用ES6 Proxy代理原始对象
- 模板中,ref自动解包,无需写.value,而reactive保持原样访问
为什么ref需要.value,而reactive不需要?
这是由javascript语言特性决定的。vue3的响应式基于ES6 Proxy,但Proxy只能代理对象,无法代理原始值(number、string、bool这些)。为了让原始值也能响应式,必须把它包装成对象,ref(0)实际上是创建了{value: 0}这个包装对象,所以访问需要.value
- 对比vue2:虽然vue2没有这个烦恼,但它把所有的data都变成了对象属性(this.count),导致this指向混乱和类型丢失。vue3的ref虽然要写.value,但解决了这些根本痛点。
什么时候用ref,什么时候用reactive?
- 数据类型上来说:简单的原始值用ref,像表单对象就用reactive
- 是否需要重新赋值:需要重新赋值用ref,不需要用reactive(不能直接赋值,会失去响应性)
- 代码组织上:组合函数返回用ref保持接口统一
- 要看团队约定一致性,一个数据源只用一种方式,避免如下混合写法
// 场景2:需要重新赋值 - 必须用 ref
const filters = ref({ keyword: '', category: 'all' })
// 可以完全替换
filters.value = { keyword: 'vue', category: 'tech' }
const userReactive = reactive({
name: 'Jack',
age: 25
})
// 不能重新赋值,会失去响应式
userReactive = {name: 'Jack', age: 30}
// 场景3:从组合函数返回 - 推荐 ref
function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment } // 保持 .value 接口统一
}
// 场景4:混合写法,不推荐
const user = reactive({
name: ref('jack'),
age: 20
})
如何将reactive转为ref
- toRef(reactiveObj, key):将reactive对象的单个属性转为ref
- toRefs(reactiveObj):将reactive对象的所有属性转为ref