to系列
toRef
用于修改响应式对象的值,但是对非响应式对象作修改其视图不会跟着变化。所以并没什么用,不过为接下来使用toRefs可以起到理解作用。
toRef会提供一个访问响应式对象的属性的ref,使得在prop的ref传递或者复合对象解构时可以保持响应式。
示例代码:
<template>
<div>
{{ rarrot }}
</div>
<hr>
<div>
{{ rarrot2 }}
</div>
<button @click="edit">修改</button>
</template>
<script setup lang='ts'>
import { toRef, reactive } from 'vue'
// toRef 只能修改响应式对象的值
const rarrot = reactive({ name: 'rarrot', age: 66 })
const name = toRef(rarrot, 'name')
// toRef 修改非响应式对象其视图不会变化
const rarrot2 = { name: 'rarrot2', age: 99 }
const name2 = toRef(rarrot2, 'name')
const edit = () => {
name.value = 'nihao'
console.log("🚀 rarrot", rarrot)
name2.value='nihao2'
console.log("🚀 rarrot2", rarrot2)
}
</script>
<style scoped></style>
toRefs
toRefs可以用于对象解构,由于响应式对象解构之后的属性是不具有响应式的,所以需要使用到toRef进行保持响应式。
使用方式的示例代码:
<template>
<div>
{{ rarrot }}
</div>
<hr>
<div>
{{ name }}---{{ age }}
</div>
<button @click="edit">修改</button>
</template>
<script setup lang='ts'>
import { reactive,toRefs } from 'vue'
const rarrot = reactive({ name: 'rarrot', age: 66 })
let { name, age } = toRefs(rarrot)
// 像以下这种直接解构视图是不会变化的
// let {name,age}=rarrot
const edit = () => {
name.value = 'nihao'
console.log("🚀 rarrot", rarrot)
}
</script>
<style scoped></style>
简易的实现toRefs,看以下示例代码:
<template>
<div>
{{ rarrot }}
</div>
<hr>
<div>
{{ name }}---{{ age }}
</div>
<button @click="edit">修改</button>
</template>
<script setup lang='ts'>
import { toRef, reactive, toRaw } from 'vue'
const rarrot = reactive({ name: 'rarrot', age: 66 })
const toRefs = <T extends object>(object: T) => {
const map: any = {}
for (let key in object) {
// const name=toRef(rarrot,'name')其实下面这行跟这一行一样,对比着来看
// 真正的源码里面是调用的propertyToRef方法,然后在调用的ObjectRefImpl方法
map[key] = toRef(object, key)
}
return map
}
// 适用于解构赋值,torefs就是按照传入参数一次性解构多个
let { name, age } = toRefs(rarrot)
const edit = () => {
console.log("初始的🚀 rarrot", rarrot);
name.value = 'nihao'
age.value = 33
// 以下这种方式也可以使得视图变化
// rarrot.name='nihao'
// rarrot.age=33
console.log("🚀 rarrot", rarrot)
}
</script>
<style scoped></style>
toRaw
toRaw()
可以从reactive(), readonly(), shallowReactive()或 shallowReadonly().创建的proxy返回原始对象。
具有两个使用场景:
- 在前面的ref源码中,就有将ref响应式对象转换为原始对象进行比较,然后观察属性值或引用是否有改变,有改变的话,就进行依赖的更新。
- 有些情况下我们需要临时获得原始对象的值,并不希望引起代理对象更改的副作用。比如说浏览器本地存储、 Cookies 等,它们不能存储 Proxy 对象,我们可以使用
toRaw()
来获取源对象并存储。
看示例:
<template>
<div>
{{ rarrot }}
</div>
<hr>
<button @click="edit">修改</button>
</template>
<script setup lang='ts'>
import { reactive, toRaw } from 'vue'
const rarrot = reactive({ name: 'rarrot', age: 66 })
const edit = () => {
console.log(rarrot,toRaw(rarrot));
// toRaw其实是跟以下代码一样的,在源码中也是这样做的
console.log(rarrot,rarrot['__v_raw']);
}
</script>
<style scoped></style>
源码解析
toRef
/**
* Used to normalize values / refs / getters into refs.
* 用于将值/引用/获取器规范化为引用。
*
* @example
*
* // returns existing refs as-is
* toRef(existingRef)
*
* // creates a ref that calls the getter on .value access
* toRef(() => props.foo)
*
* // creates normal refs from non-function values
* // equivalent to ref(1)
* toRef(1)
*
*
* 还可用于为源反应对象上的属性创建引用。
* 创建的引用与其源属性同步:改变源
* 属性将更新引用,反之亦然。
*
* @example
* 前面给出了示例
*
* @param source - A getter, an existing ref, a non-function value, or a
* reactive object to create a property ref from.
* @param [key] - (optional) Name of the property in the reactive object.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#toref}
*/
// 当只传一个参数value时,若参数为普通值,返回包含这个值的Ref;若已经是Ref,返回自身
export function toRef<T>(
value: T
): T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
? T
: Ref<UnwrapRef<T>>
// 传两个参数,第一个参数为引用类型,第二个参数为对象的键名,也是属性名
// 返回ToRef类型,包含指定对象原属性的Ref
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): ToRef<T[K]>
// 传三个参数,第三个参数为默认值,返回包含默认值的ToRef
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>
// 以下函数为实现代码,会根据参数不同返回不同结果,主要用于将对象的属性转换为ref对象
export function toRef(
source: Record<string, any> | MaybeRef,
key?: string,
defaultValue?: unknown
): Ref {
// 判断是否为Ref,是就直接返回源ref对象
if (isRef(source)) {
return source
} else if (isFunction(source)) {
// 判断是一个函数,就用GetterRefImpl类创建一个ref对象返回
// 这个类实现了一个可以惰性获取值的ref
return new GetterRefImpl(source) as any
} else if (isObject(source) && arguments.length > 1) {
// 判断是否为引用类型,是就调用以下函数
return propertyToRef(source, key!, defaultValue)
} else {
return ref(source)
}
}
以上代码中会去调用propertyToRef(source, key!, defaultValue)
。
GetterRefImpl(source)
和ToRef
类型的源代码如下:
class GetterRefImpl<T> {
public readonly __v_isRef = true
public readonly __v_isReadonly = true
constructor(private readonly _getter: () => T) { }
get value() {
return this._getter()
}
}
export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
propertyToRef(source, key!, defaultValue)
的源代码如下:
function propertyToRef(
source: Record<string, any>,
key: string,
defaultValue?: unknown
) {
const val = source[key]
return isRef(val)
? val
: (new ObjectRefImpl(source, key, defaultValue) as any)
}
以上代码先从源对象source中获取key属性的值val,用val进行判断是否为ref对象,不是就调用ObjectRefImpl(source, key, defaultValue)
。
ObjectRefImpl(source, key, defaultValue)
的源代码如下:
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! : val
}
set value(newVal) {
this._object[this._key] = newVal
}
get dep(): Dep | undefined {
return getDepFromReactive(toRaw(this._object), this._key)
}
}
以下函数类似于ref中的RefImpl()
,只不过RefImpl里面进行了收集依赖和更新依赖,而这里没有,这也是为什么普通对象不会有视图的变化,响应式对象使用reactive,reactive里面语句会进行收集依赖和更新依赖的操作。而这里实现了一个可以跟踪源对象属性变化的ref,并且也有get和set实现响应式。
toRefs
跟前面简易写一个toRefs做的操作是一样的,只不过会先调用propertyToRef(object, key)
判断对象的每个属性是否为ref对象。再跟上面toRef一样的流程,只不过这里对每个解构的属性都进行了一遍。
/**
*
* 将响应式对象中每个属性都转换为普通对象,也就是解构
* 结果对象是一个指向相应属性的引用
* 原始对象。每个单独的引用都是使用 {@link toRef()} 创建的。
*
* @param object - Reactive object to be made into an object of linked refs.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#torefs}
*/
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)
}
return ret
}
toRaw
用于转换为原始对象,实际就是通过将原始对象存入到proxy中,使用ReactiveFlags.RAW
的__v_raw参数可取出原始参数。
/**
* Returns the raw, original object of a Vue-created proxy.
*
* `toRaw()` can return the original object from proxies created by
* {@link reactive()}, {@link readonly()}, {@link shallowReactive()} or
* {@link shallowReadonly()}.
*
* This is an escape hatch that can be used to temporarily read without
* incurring proxy access / tracking overhead or write without triggering
* changes. It is **not** recommended to hold a persistent reference to the
* original object. Use with caution.
*
* @example
*
* const foo = {}
* const reactiveFoo = reactive(foo)
*
* console.log(toRaw(reactiveFoo) === foo) // true
*
*
* @param observed - The object for which the "raw" value is requested.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#toraw}
*/
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}