ref
import { ref, onMounted } from 'vue';
import type { Ref } from 'vue'
type Q = {
name: string;
}
// ref
const data: Ref<Q> = ref({ name: "小明" })
const changeData = function () {
data.value.name = '小张';
console.log(data.value);
}
const pDom = ref<HTMLElement>()
onMounted(() => {
console.log(pDom.value?.innerHTML);
console.log(pDom.value);
})
<div>
<p ref="pDom"> dom 元素 </p>
<button @click="changeData">修改</button>
<div>{{ data.name }}</div>
</div>
效果图:
isRef
import { ref, isRef } from 'vue';
const data = ref('我是ref数据')
const data2 = { name: "郭嘉", str: "我不是ref数据" }
const _isRef = isRef(data)
const _isRef2 = isRef(data2)
<div>
<div>data是不是ref数据? -> {{ _isRef? '是': '不是' }}</div>
<div>data2是不是ref数据? -> {{ _isRef2? '是': '不是' }}</div>
</div>
效果图:
shallowRef
import { shallowRef } from 'vue';
const data = shallowRef({ name: "刘备" })
const changeData = function () {
console.log('修改前: ', data.value);
data.value.name = '曹操'
console.log('修改后: ', data.value);
}
<div>
<div>{{ data.name }}</div>
<button @click="changeData">修改name</button>
</div>
效果图:
通过上面这种写法可以看到,点击'修改name'按钮之后,data中的值虽然发生改变了,但是没有更新视图,需要改成下面这种写法:
import { shallowRef } from 'vue';
const data = shallowRef({ name: "刘备" })
const changeData = function () {
console.log('修改前: ', data.value);
data.value = { name: '曹操' } // <===================================================
console.log('修改后: ', data.value);
}
原因是 shallowRef
只有对.value
的访问是响应式的(浅层响应式)
效果图:
triggerRef
import { shallowRef, triggerRef } from 'vue';
const data = shallowRef({ name: "貂蝉" })
const changeData = function () {
console.log('修改前: ', data.value);
data.value.name = '陆逊'
// triggerRef(data)
console.log('修改后: ', data.value);
}
<template>
<div>
<div>{{ data.name }}</div>
<button @click="changeData">修改</button>
</div>
</template>
不使用triggerRef
效果图:
可以看到,和shallowRef
的效果一模一样,使用triggerRef
将代码修改为:
import { shallowRef, triggerRef } from 'vue';
const data = shallowRef({ name: "貂蝉" })
const changeData = function () {
console.log('修改前: ', data.value);
data.value.name = '陆逊'
triggerRef(data) // <------------------------
console.log('修改后: ', data.value);
}
使用triggerRef
效果图:
customRef
跟ref
内部实现原理很像,最麻烦的一个,先上代码:
import { customRef } from 'vue'
import type { Ref } from 'vue';
const _Ref = function <T>(value: T): Ref<T> {
return customRef((track, trigger) => {
return {
get() {
console.log('get方法被调用');
track()
return value
},
set(newValue) {
console.log('set方法被调用,新值为: ', newValue);
value = newValue
trigger()
}
}
})
}
const data = _Ref({ name: "张辽" })
<div>
<div>{{ data.name }}</div>
</div>
效果图:
现在我们来尝试修改一下,data
中的数据:
import { customRef } from 'vue'
import type { Ref } from 'vue';
const _Ref = function <T>(value: T): Ref<T> {
return customRef((track, trigger) => {
return {
get() {
console.log('get方法被调用');
track()
return value
},
set(newValue) {
console.log('set方法被调用,新值为: ', newValue);
value = newValue
trigger()
}
}
})
}
const data = _Ref({ name: "张辽" })
const changeData = function () {
console.log('修改前: ', data.value);
data.value = { name: '孙权' }
console.log('修改后: ', data.value);
}
<div>
<div>{{ data.name }}</div>
<button @click="changeData">修改</button>
</div>
效果图:
注意: 值类型必须保持一致! 上述例子中用的是
object
类型,新值类型也必须为object
小结
ref
: 返回一个响应式数据,可更改的ref
对象
isRef
: 判断某个值是不是 ref
对象
shallowRef
: 浅层响应式,修改值时,不可以和 ref
对象写在一起!
triggerRef
: 强制更新收集的依赖
customRef
: 自定义 ref
关于 shallowRef
为什么不能和 ref
写在一起:
import { ref, shallowRef } from 'vue'
const refData = ref({ name: "张角" })
const shallowRefData = shallowRef({ name: "董卓" })
const changeData = function () {
console.log("修改前: ", refData.value);
console.log("修改前: ", shallowRefData.value);
refData.value.name = "马超"
shallowRefData.value.name = "马云禄"
console.log("修改后: ", refData.value);
console.log("修改后: ", shallowRefData.value);
}
<div>
<div>{{ refData.name }} -- {{ shallowRefData.name }}</div>
<button @click="changeData">修改</button>
</div>
效果图:
上述例子中,我们把修改shallowRef
和ref
的值放在了一起,可以看到shallowRef
的值同样被修改了(会更新视图),至于为什么会这样,那是因为vue3源码中在ref
的内部调用了triggerRef
方法
这两个东西,千万不要写在一块!!!
下载好vue3源码之后,文件地址:
ref 源码 : \core-main\packages\reactivity\src\ref.ts
export function ref<T extends object>(
value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value, false)
}
函数重载,去调用createRef
方法
createRef
方法:
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
这个方法首先判断我们传进来的值,是不是一个ref
对象,如果是,就直接return
这个对象,如果不是,直接创建ref
对象
RefImpl
类:
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
返回的就是一个'类'
其中`_value`就是我们要的值
`__v_isShallow`是我们调用`createRef`方法传递的第二个参数
拿着这个布尔值进行判断,如果为真,返回值本身,如果为假,调用 `toReactive` 方法
toReactive
方法:
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
这个函数会判断我们传入的值是不是一个引用类型
如果是引用类型,那么就调用`reactive`方法
如果不是,直接返回值本身, `reactive`方法后面再写,暂时先不写了
RefImpl
类中的 get
、set
方法跟我们customRef
中的很像
源码中shallowRef
就是把调用createRef
方法的第二个参数置为了true
export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
export function shallowRef<T extends object>(
value: T
): T extends Ref ? T : ShallowRef<T>
export function shallowRef<T>(value: T): ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
return createRef(value, true) // <--------------------
}
所以`shallowRef`是不会被调用`toReactive`方法的
响应式只到`.value`
上面说到triggerRef
会更新shallowRef
的值,在我们看过源码之后,发现set
值的时候,是去调用了triggerRefValue
方法,而我们使用ref
和triggerRef
去set
值的时候,其实调用的也是triggerRefValue
方法,而这个方法内部会去更新我们的依赖