# Ref
在Vue3.x中Ref类型的对象也是一个响应式对象,Ref类型的对象通过ref方法创建
// 从Ref的类型可以看出,Ref对象上存在一个用来标记的RefSymbol值为true
// 并且将传入ref api的值保存在value属性上
export interface Ref<T = any> {
[RefSymbol]: true
value: T
}
// ref和shallowRef都可以创建Ref类型的对象
// 它们内部都是调用createRef方法
export function ref(value?: unknown) {
return createRef(value)
}
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
function createRef(rawValue: unknown, shallow = false) {
// 当传入的value是一个已经ref对象时,直接返回
if (isRef(rawValue)) {
return rawValue
}
// 如果传入的rawValue是一个对象,并且不是创建shallowRef
// 那么会调用convert将rawValue转化成响应式对象
let value = shallow ? rawValue : convert(rawValue)
// 返回ref对象,定义了value的getter、setter
const r = {
__v_isRef: true,
get value() {
// 在访问value时会收集依赖,
track(r, TrackOpTypes.GET, 'value')
return value
},
set value(newVal) {
// 判断设置的新值是否和旧值相等
// 如果newVal是一个代理对象,那么应该拿到newVal的原始对象去判断
if (hasChanged(toRaw(newVal), rawValue)) {
rawValue = newVal
// newVal,并且不是创建shallowRef
// 那么会调用convert将newVal转化成响应式对象
value = shallow ? newVal : convert(newVal)
trigger(r, TriggerOpTypes.SET, 'value', newVal)
}
}
}
return r
}
以上就是ref的核心代码,可以看到它就是在一个普通对象上定义了value属性的getter和setter,并且在getter和setter中 派发更新和收集依赖,这使得ref也具有响应式对象的特性,那么为什么有了响应式对象还需要多一个Ref类型的对象呢? 到底什么时候应该用ref什么时候应该用reactive呢?
我们可以从之前响应式对象的实现看出,创建响应式对象时必须传入的是一个对象才行,那么当我们想将一个基础类型的值也变成响应式时, 就需要将它放在一个对象内,这样其实是很不方便的:
const a = 1
const reactiveA = reactive({ a })
所以ref提供了一个将基础类型的值也转化成响应式对象的方法:
const refA = ref(1)
并且在之后使用Vue3.x时,我们经常会使用Hooks来整合一些逻辑:
function useXXX () {
const state = reactive({
foo: 1,
bar: 2
})
return state
}
export default {
setup() {
const { foo, bar } = useXXX()
return {
foo,
bar
}
}
})
在上面的例子中,我们可能想当然的以为通过解构赋值拿到useXXX中创建响应式对象的值应该也是响应式的,其实这样写是很有问题的, 这里拿到的foo和bar只是一个基础类型的变量,当我们将foo和bar从setup中返回并且在模板中使用时,修改foo和bar的值是不能触发 模板的更新的。所以,在这种情况下,我们需要维持foo和bar响应式的特性,这时ref就可以派上用场了:
function useXXX () {
const state = reactive({
foo: 1,
bar: 2
})
return toRefs(state)
}
我们使用toRefs api将state中的每个key的值都转化为一个ref对象,这样通过解构赋值拿到的foo和bar就不会失去响应式对象的特性, 这也是ref的另一个用途。
# 类型
export interface Ref<T = any> {
[RefSymbol]: true
value: T
}
/**
* 当ref没有传值时,返回的类型为Ref<T | undefined>,也就是说ref对象上value的类型应该为T或者undefined
* 如果传入的value本身是一个ref对象,直接返回value本身的类型,因为在api的实现中,传入一个ref对象会直接返回对象本身
* 除了以上情况,其他都是返回Ref<UnwrapRef<T>>类型,将传入的类型T通过UnwrapRef解引用,再将解引用的类型传到Ref中
* UnwrapRef在之前响应式的章节已经介绍过。
* 这里举一个例子:
* const a = ref({
* a: 1,
* b: {
* c: ref(3),
* d: ref([1, 2, 3])
* }
* })
* 此时通过解引用后a的类型应该为
* Ref<{
* a: number;
* b: {
* c: number;
* d: number[];
* };
* }>
* 这里可能会有疑问,为什么在ref中嵌套了ref后d的类型不是Ref<number[]>而是number[]
* 因为在上面api的实现中,当传入ref的是一个对象时,会通过convert将对象转化成响应式对象
* 而在响应式对象中,ref是默认展开的!
*/
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>