toRef

基于响应式对象上的一个属性创建一个对应的ref,这样创建的ref与其源属性保持同步。

改变源属性的值将更新ref的值,反之亦然。

如果对象是非响应式的,则不产生效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<div>{{ person }}</div>
<div>toRef: {{ like }}</div>
<hr>
<div>{{ person2 }}</div>
<div>toRef: {{ like2 }}</div>
<hr>
<div>
<button @click="change">修改</button>
</div>
</template>

<script setup lang="ts">
import { toRef, reactive } from 'vue'

const person = {
name: "yajue",
age: 24,
like: "ramen"
}
// 非响应式对象经过toRef处理
// 参数1为对象,2为对象中的一个key
const like = toRef(person, "like")

const person2 = reactive({
name: "yajue",
age: 24,
like: "ramen"
})
// 响应式对象经过toRef处理
const like2 = toRef(person2, "like")

const change = ()=> {
// 如果对象不是响应式,则值改变不会造成视图的改变
// person.like = "rape"
// console.log(person)

// toRef作用于非响应式,然并卵,对象本身变化了,但视图不变化
like.value = "rape"
console.log(person, like)

// toRef作用于响应式,对象和视图都变化了
like2.value = "rape"
console.log(person2, like2)

// 应用场景:只需要响应式对象上的某属性,并且希望它的改变能导致对象和视图改变
}
</script>

toRefs

根据一个响应式对象创建普通对象,对象的每个属性都是指向原对象属性的ref,主要是方便解构使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<div>{{ person }}</div>
<hr>
<div>{{ name }} - {{ age }} - {{ like }}</div>
<hr>
<div>
<button @click="change">修改</button>
</div>
</template>

<script setup lang="ts">
import { toRef, reactive, toRefs } from 'vue'

const person = reactive({
name: "yajue",
age: 24,
like: "ramen"
})

// toRefs可以把对象的每一个属性都变成ref,
// const toRefs = <T extends object>(object: T)=> {
// const map: any = {}
// for(let key in object) {
// map[key] = toRef(object, key)
// }
// return map
// }

// reactive一旦被解构,响应式就不存在了(变成普通值)
// let {name, age, like} = person
// 如果想要从响应式对象上解构响应式值,需要toRefs
const {name, age, like} = toRefs(person)

const change = ()=> {
console.log(name, age, like)
// name = "yjsp"
// age = 114
// like = "rape"
name.value = "yjsp"
age.value = 114
like.value = "rape"
}
</script>

toRaw

返回响应式对象的原始对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<button @click="look">查看</button>
</div>
</template>

<script setup lang="ts">
import { reactive, toRaw } from 'vue'

const person = reactive<any>({
name: "yajue",
age: 24,
like: "ramen"
})

const look = ()=> {
// toRaw能把响应式对象变成普通对象
console.log(person, toRaw(person))
// 本质上就是这样(__v_raw是一个隐藏属性)
console.log(person, person["__v_raw"])
}
</script>

源码

在Vue源码(/package/reactivity/src/ref.ts)中可以看到toRef和toRefs的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 前面是各种对函数的重载
export function toRef<T>(
value: T
): T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
? T
: Ref<UnwrapRef<T>>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): ToRef<T[K]>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>
export function toRef(
source: Record<string, any> | MaybeRef, // 第一个参数:一个对象
key?: string, // 第二个参数:对象的一个key值
defaultValue?: unknown
): Ref {
if (isRef(source)) {
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] // 取出key值的value
return isRef(val) // 判断value是否为ref对象
? val // 是就直接返回
: (new ObjectRefImpl( // 否则变成ref
source as Record<string, any>,
key,
defaultValue
) as any)
}

// ------------------------------------------------

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]
) {}

// 这里没有做收集依赖或者触发依赖更新,所以它对非响应式对象不会改变视图
// 对响应式对象能改变视图,是因为reactive里会用proxy,里面有做收集依赖和触发依赖更新的的操作
// 如果这里做了,那么就会产生做两次收集或触发的bug(toRef的属性做一次,reactive对象本身再做一次)

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,就是给对象的每个属性都toRef
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
}

在Vue源码(/package/reactivity/src/reactive.ts)中可以看到toRaw的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 它从对象里取了一个属性,这个属性就是__v_raw
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'
}