Quelle est la différence entre réactif et ref dans
vue ? L'article suivant vous plongera dans le code source pour bien comprendre la différence entre réactif et ref dans vue3. J'espère qu'il vous sera utile !
Dans le développement quotidien de vue3, j'ai constaté que de nombreuses personnes utilisent reactive
ou ref
en fonction de leurs propres habitudes, bien que cela puisse répondre à leurs besoins, en cela. Dans ce cas, pourquoi devons-nous concevoir un autre ref
alors que nous avons déjà reactive
? Quels sont les scénarios d’application réels et les différences entre les deux ? reactive
或ref
一把梭,虽然这样都可以实现需求,既然这样那为什么已经有了reactive
还需要再去设计一个ref
呢?这两者的实际运用场景以及区别是什么呢?
并且关于ref
的底层逻辑,有的人说ref
的底层逻辑还是reactive
。有的人说ref
的底层是class
,value
只是这个class
的一个属性,那这两种说法哪种正确呢?都有没有依据呢?
抱着这样的疑问我们本次就深入源码,彻底搞清vue3中reactive
和ref
的区别。(学习视频分享:vue视频教程)
不想看源码的童鞋,可以直接拉到后面看总结
源码地址:packages/reactivity/reactive.ts
首先我们看一下vue3
中用来标记目标对象target
类型的ReactiveFlags
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
reactive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
reactive
函数接收一个target
对象,如果target
对象只读则直接返回该对象
若非只读则直接通过createReactiveObject
创建observe
对象
createReactiveObject
看着长不要怕,先贴createReactiveObject
完整代码,我们分段阅读
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 |
|
首先我们看到createReactiveObject
接收了五个参数
1 2 3 4 5 |
|
target 目标对象
isReadonly 是否只读
baseHandlers 基本类型的 handlers 处理数组,对象
collectionHandlers 处理 set、map、weakSet、weakMap
proxyMap WeakMap数据结构存储副作用函数
这里主要是通过ReactiveFlags.RAW
和ReactiveFlags.IS_REACTIVE
判断是否是响应式数据,若是则直接返回该对象
1 2 3 4 5 6 |
|
对于已经是Proxy
的,则直接从WeakMap
数据结构中取出这个Proxy对象并返回
1 2 3 4 |
|
这里则是校验了一下当前target
的类型是不是Object
、Array
、Map
、Set
、WeakMap
、WeakSet
,如果都不是则直接返回该对象,不做响应式处理
1 2 3 4 5 |
|
校验类型的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
所有的前置校验完后,就可以使用proxy
代理target
对象了
这里使用了一个三目运算符
通过TargetType.COLLECTION
来执行不同的处理逻辑
collectionHandlers
baseHandlers
1 2 3 4 5 6 7 8 9 |
|
现在对createReactiveObject
的执行逻辑是不是就很清晰了
到这里还没有结束,createReactiveObject
中最后proxy
是如何去代理target
的呢?这里我们用baseHandlers
举例,深入baseHandlers
的内部去看看
baseHandlers
源码地址:packages/reactivity/baseHandlers.ts
在reactive.ts
中我们可以看到一共引入了四种 handler
1 2 3 4 5 6 |
|
mutableHandlers
可变处理readonlyHandlers
只读处理shallowReactiveHandlers
浅观察处理(只观察目标对象的第一层属性)shallowReadonlyHandlers
浅观察 && 只读我们以mutableHandlers
为例
1 2 3 4 5 6 7 8 9 10 11 12 |
|
这里的get
和set
分别对应着createGetter()
、createSetter()
createGetter()
先上完整版代码
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 |
|
看着长,最终就是track()
依赖收集
Et concernant la logique sous-jacente de
track()
依赖收集内容过多,和trigger()
ref
, certaines personnes disent que la logique sous-jacente deref
est toujoursréactive
. Certaines personnes disent que la couche inférieure deref
estclass
et quevalue
n'est qu'un attribut de cetteclass
. Alors ces deux-là. Quelle affirmation est correcte ? Y a-t-il une base pour cela ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
createReactiveObject
est-elle maintenant la même ? C'est très clair🎜🎜Ce n'est pas encore fini. Comment fonctionne le dernier ? proxy
dans createReactiveObject
proxy target
? Ici, nous utilisons baseHandlers
comme exemple et jetons un œil à l'intérieur de baseHandlers
🎜🎜🎜baseHandlers
🎜🎜🎜Adresse du code source : packages/reactivity/baseHandlers.ts
🎜🎜Dans reactive.ts
, nous pouvons voir qu'un total de quatre gestionnaires ont été introduit🎜 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
mutableHandlers
Traitement des variables🎜readonlyHandlers
Traitement en lecture seule🎜shallowReactiveHandlers
Traitement d'observation superficiel (Observez uniquement les propriétés de premier niveau de l'objet cible)🎜shallowReadonlyHandlers
Observation superficielle && lecture seulemutableHandlers
à titre d'exemple🎜1 2 3 4 5 |
|
get
et set
correspondent ici à createGetter()
et createSetter()
respectivement 🎜🎜🎜🎜🎜createGetter ()🎜🎜🎜Allez d'abord à la version complète du code🎜rrreee🎜Ça a l'air long, et finalement c'est la dépendance track()
collection🎜🎜track()
Repose sur la collecte de trop de contenu Avectrigger()
déclenchant des mises à jour, ouvrez un article séparé🎜🎜🎜🎜🎜🎜createSetter( )🎜🎜
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
/**
* 拦截对象的设置属性操作
* @param shallow 是否是浅观察
* @returns
*/
function
createSetter(shallow = false) {
/**
* @param target 目标对象
* @param key 设置的属性名称
* @param value 要改变的属性值
* @param receiver 如果遇到setter,receiver则为setter调用时的this值
*/
return
function
set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target
as
any)[key]
// 如果模式不是浅观察模式
if
(!shallow) {
// 拿新值和老值的原始值,因为新传入的值可能是响应式数据,如果直接和 target 上原始值比较是没有意义的
value = toRaw(value)
oldValue = toRaw(oldValue)
// 目标对象不是数组,旧值是ref,新值不是ref,则直接赋值,这里提到ref
if
(!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return
true
}
}
else
{
// in shallow mode, objects are set as-is regardless of reactive or not
}
// 检查对象是否有这个属性
const
hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) <p><code>trigger()</code>触发更新</p><h2 data-id=
"heading-7"
><strong>ref</strong></h2><p>源码地址:<code>packages/reactivity/src/ref.ts</code></p><p>接收一个可选<code>unknown</code>,接着直接调用<code>createRef()</code></p><pre
class
=
"brush:php;toolbar:false"
>export
function
ref(value?: unknown) {
return
createRef(value, false)
}
Copier après la connexion
与
ref
的区别就是在调用createRef()
时第二个值传的是true
1
2
3
export
function
shallowRef(value?: unknown) {
return
createRef(value, true)
}
Copier après la connexionCopier après la connexion看一下官方文档上对
shallowRef
的解释
createRef
通过
isRef()
判断是否是ref
数据,是则直接返回该数据,不是则通过new RefImpl
创建ref数据在创建时会传两个值一个是
rawValue
(原始值),一个是shallow
(是否是浅观察),具体使用场景可看上面ref
和shallowRef
的介绍
1
2
3
4
5
6
7
function
createRef(rawValue: unknown, shallow: boolean) {
// 是否是 ref 数据
if
(isRef(rawValue)) {
return
rawValue
}
return
new
RefImpl(rawValue, shallow)
}
Copier après la connexionCopier après la connexion
isRef()
通过
__v_isRef
只读属性判断是否是ref数据,此属性会在RefImpl
创建ref数据时添加
1
2
3
export
function
isRef(r: any): r is Ref {
return
Boolean(r && r.__v_isRef === true)
}
Copier après la connexionCopier après la connexionRefImpl
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
class
RefImpl<t> {
private
_value: T
private
_rawValue: T
public
dep?: Dep = undefined
// 只读属性 __v_isRef 判断是否是ref数据的静态标识
public
readonly __v_isRef = true
constructor(value: T,
public
readonly _shallow: boolean) {
this._rawValue = _shallow ? value : toRaw(value)
// 非浅观察用toRaw()包裹原始值
this._value = _shallow ? value : toReactive(value)
// 非浅观察用toReactive()处理数据
}
get value() {
// 依赖收集
trackRefValue(this)
return
this._value
}
set value(newVal) {
newVal = this._shallow ? newVal : toRaw(newVal)
// 非浅观察用toRaw()包裹值
// 两个值不相等
if
(hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
// 触发依赖,派发更新
}
}
}</t>
Copier après la connexionCopier après la connexion根据
RefImpl
我们可以看到ref
的底层逻辑,如果是对象确实会使用reactive
进行处理,并且ref
的创建使用的也是RefImpl
class实例,value只是RefImpl
的属性在我们
访问
和设置
ref
的value值时,也分别是通过get
和set
拦截进行依赖收集
和派发更新
的
toReactive
我们来看一下
toReactive()
这个方法,在RefImpl
中创建ref
数据时会调用toReactive()
方法,这里会先判断传进来的值是不是对象,如果是就用reactive()
包裹,否则就返回其本身
1
2
export
const
toReactive = <t>(value: T): T =>
isObject(value) ? reactive(value) : value</t>
Copier après la connexionCopier après la connexion
trackRefValue
ref的依赖收集方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export
function
trackRefValue(ref: RefBase<any>) {
if
(isTracking()) {
ref = toRaw(ref)
if
(!ref.dep) {
ref.dep = createDep()
}
if
(__DEV__) {
trackEffects(ref.dep, {
target: ref,
type: TrackOpTypes.GET,
key:
'value'
})
}
else
{
trackEffects(ref.dep)
}
}
}</any>
Copier après la connexionCopier après la connexion
triggerRefValue
ref的派发更新方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export
function
triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref)
if
(ref.dep) {
if
(__DEV__) {
triggerEffects(ref.dep, {
target: ref,
type: TriggerOpTypes.SET,
key:
'value'
,
newValue: newVal
})
}
else
{
triggerEffects(ref.dep)
}
}
}</any>
Copier après la connexionCopier après la connexion总结
看完
reactive
和ref
源码,相信对本文一开始的几个问题也都有了答案,这里也总结了几个问题:
- 问:ref的底层逻辑是什么,具体是如何实现的
答:ref底层会通过
new RefImpl()
来创造ref数据,在new RefImpl()
会首先给数据添加__v_isRef
只读属性用来标识ref
数据。而后判断传入的值是否是对象,如果是对象则使用toReactive()
处理成reactive
,并将值赋给RefImpl()
的value
属性上。在访问
和设置
ref数据的value
时会分别触发依赖收集
和派发更新
流程。
- 问:ref底层是否会使用
reactive
处理数据答:RefImpl中非浅观察会调用
toReactive()
方法处理数据,toReactive()
中会先判断传入的值是不是一个对象,如果是对象则使用reactive
进行处理,不是则直接返回值本身。
- 问:为什么已经有了
reactive
还需要在设计一个ref
呢?答: 因为vue3响应式方案使用的是
proxy
,而proxy
的代理目标必须是非原始值,没有任何方式能去拦截对原始值
的操作,所以就需要一层对象作为包裹,间接实现原始值的响应式方案。
- 问:为什么
ref
数据必须要有个value
属性,访问ref数据必须要通过.value
的方式呢?答:这是因为要解决
响应式丢失的问题
,举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// obj是响应式数据
const
obj = reactive({ foo: 1, bar: 2 })
// newObj 对象下具有与 obj对象同名的属性,并且每个属性值都是一个对象
// 该对象具有一个访问器属性 value,当读取 value的值时,其实读取的是 obj 对象下相应的属性值
const
newObj = {
foo: {
get value() {
return
obj.foo
}
},
bar: {
get value() {
return
obj.bar
}
}
}
effect(() => {
// 在副作用函数内通过新对象 newObj 读取 foo 的属性值
console.log(newObj.foo)
})
// 正常触发响应
obj.foo = 100
Copier après la connexionCopier après la connexion可以看到,在现在的
newObj
对象下,具有与obj
对象同名的属性,而且每个属性的值都是一个对象,例如foo 属性的值是:
1
2
3
4
5
{
get value() {
return
obj.foo
}
}
Copier après la connexionCopier après la connexion该对象有一个访问器属性
value
,当读取value的值时,最终读取的是响应式数据obj下的同名属性值
。也就是说,当在副作用函数内读取newObj.foo
时,等价于间接读取了obj.foo
的值。这样响应式数据就能够与副作用函数建立响应联系(Partage de vidéos d'apprentissage : Développement web front-end, Vidéo de programmation de base)
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!