toRef
可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,ref 函数也可以转换,但值非关联。
访问对象属性时 记得使用 toRef 或者 toRefs进行解构
// 引用场景
const person = reactive({
name: '小张',
age: 12
})
// 双向 ref,会与源属性同步
const nameToRef = toRef(person, 'name')
const change = () => {
nameToRef.value = '大张'
console.log(person) // { name: '大张', age: 12 }
// 更改源属性也会更新该 ref
person.name = '无名'
console.log(nameToRef.value) // 无名
}
toRef 源码解析
如果是ref 对象则直接返回,如果不是则通过ObjectRefImpl创建一个类ref对象
export function toRef(
source: Record<string, any> | MaybeRef,
key?: string,
defaultValue?: unknown
): Ref {
if (isRef(source)) {
// 以及是一个ref对象了就直接返回
return source
} else if (isFunction(source)) {
// 是函数执行这里
return new GetterRefImpl(source) as any
} else if (isObject(source) && arguments.length > 1) {
// 是一个引用类型且不是空 调用下面的方法
return propertyToRef(source, key!, defaultValue)
} else {
return ref(source)
}
}
function propertyToRef(source: object, key: string, defaultValue?: unknown) {
const val = (source as any)[key]
return isRef(val)
? val
: (new ObjectRefImpl( // 通过ObjectRefImpl创建一个类ref、ObjectRefImpl源码在下面
source as Record<string, any>,
key,
defaultValue
) as any)
}
类ref对象只是做了值的改变, 并未处理收集依赖和触发依赖的过程,所以普通对象无法更新视图。 与ref的源码类似,不过ref是通过RefImpl类创建,里面处理了收集依赖和触发依赖的过程
ObjectRefImpl
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K]
) {}
get value() {
const val = this._object[this._key]
return val === undefined ? (this._defaultValue as T[K]) : val
}
set value(newVal) {
this._object[this._key] = newVal
}
get dep(): Dep | undefined {
return getDepFromReactive(toRaw(this._object), this._key)
}
}
toRefs
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
配合解构使用
import { reactive, toRefs } from 'vue'
const state = reactive({
foo: 1,
bar: 1
})
let { foo, bar } = state(obj)
foo.value++
bar.value = 5
console.log(state.foo, state.bar); // 2, 5
toRefs 源码解析
把reactive 对象的每一个属性通过对象循环调用toRef()都变成了ref
export function toRefs<T extends object>(object: T): ToRefs<T> {
// 如果不是响应式对象就直接警告
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = propertyToRef(object, key) // 通过toRef()把响应式对象的每个属性都变成 ref
}
return ret
}
toRaw
根据一个 Vue 创建的代理返回其原始对象,也就是将响应式对象转化为普通对象
import { reactive, toRaw } from 'vue'
const state = reactive({
foo: 1,
bar: 1
})
const obj = toRaw(state)
// 响应式对象转化为普通对象
console.log(state, obj) // proxy object
console.log(state['__v_raw']) // 可以直接取到原始对象,具体可以看下面源码
toRaw 源码解析
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}
export function toRaw<T>(observed: T): T {
// 通过 ReactiveFlags 枚举值取出 proxy对象的原始对象
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
toValue
将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1