Vue3 ref和reactive的区别
一、reactive
数据类型
reactive()
可用于创造一个响应式对象,它接受一个参数,这个参数的类型是一个重点,接下来我们先看看Vue3的源码里是怎么处理的。
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// ...
}
Vue3主要是调用createReactiveObject
函数来创建reactive对象,从源码里可以看到,该函数会首先判断reactive()
传入的参数是不是一个对象,如果不是则不做任何处理,直接返回原值,同时开发环境下会在控制台输出一句警告。所以严格上来说,reactive()
并不是像有些人说的不能传入基本类型的参数,它可以传,只是这种数据会失去响应式。
let name = reactive('张三')
setTimeout(() => {
name = '李四'
console.log(name, 'name') // 李四
}, 3000)
<template>
<h1>{{ name }}</h1>
</template>
上面模板里仍然显示的是张三,因为name没有响应式不会触发视图更新。
原始数据与响应式数据
我们可以先定义一个原始对象,再将对象传入reactive()
,此时原始对象和响应式对象会互相干扰。
let data = { name: '张三' } // 原始对象
let state = reactive(data)
setTimeout(() => {
state.name = '李四'
console.log(data.name) // 李四
}, 2000)
修改响应式对象state.name
的值,原始对象data.name
也会同步变化。
反过来也是一样,修改data.name
,state.name
也会同步变化,但需要注意的是:这种情况下虽然state.name
发生了改变但视图并不会更新
。
let data = { name: '张三' }
let state = reactive(data)
setTimeout(() => {
data.name = '李四'
console.log(state.name) // 李四,但template里仍为张三
}, 2000)
</script>
<template>
<h1>{{ state.name }}</h1>
</template>
所以如果你是通过定义一个对象再将对象传入reactive的话,建议始终是对响应式对象进行操作而不是原始数据。
二、ref
数据类型
export function ref(value?: unknown) {
return createRef(value, false)
}
从源码里可以知道,ref()
接受一个参数,参数可以是任意类型
,这也是ref与reactive的区别之一,不管是基本类型还是引用类型ref
都具备响应式,另外ref
要通过.value
进行取值。
let name = ref('张三') // 可以是基本类型
// 也可以是引用类型
let user = ref({
name: '张三',
age: 18
})
console.log(name.value)
console.log(user.value)
原始数据与响应式数据
ref
与reactive
一样,修改原始数据或响应式数据都会同步改变对方的值,且在修改原始对象时都无法触发视图更新。
ref也可能是一种reactive
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
通过源码可以知道,ref
是通过RefImpl
这个类来创建的,而在RefImpl
内部会调用toReactive
函数,如果不是基本类型的数据,会将其转换成reactive
。
可重新赋值对象
使用reactive
时,对响应式对象重新赋值是会失去响应式的。
let state = reactive({ name: '张三' })
setTimeout(() => {
state = { name: '李四'}
console.log(state.name) // 李四,数据变更了但视图不会更新
}, 2000)
ref
则没有这种问题。
let state = ref({ name: '张三' })
setTimeout(() => {
state.value = { name: '李四'}
console.log(state.value.name) // 数据变更,同时视图也会更新
}, 2000)
使用watch监听ref对象需要deep
let state = ref({ name: '张三' })
setTimeout(() => {
state.value.name = '李四'
}, 2000)
watch(state, () => {
console.log(state.value.name)
}, { deep: true })
reactive不加deep: true
watch也会触发,而ref则需要手动加上。
小结
- 对于响应式而言,ref支持对象类型和基本类型,而reactive只支持对象类型
- 当ref是对象类型的时候,本质上也是一个reactive
- 对象类型的ref和reactive,其响应式底层原理都是Proxy
转载自:https://juejin.cn/post/7249281117152510008