详解vue3中reactive和ref的区别(源码解析)

广告:宝塔服务器面板,一键全能部署及管理,送你10850元礼包,点我领取~~~

详解vue3中reactive和ref的区别(源码解析)

vue中reactive和ref的区别是什么?下面本篇文章带大家深入源码彻底搞清vue3中reactive和ref的区别,希望对大家有所帮助!

在vue3的日常开发中,我发现很多人都是基于自己的习惯reactiveref一把梭,虽然这样都可以实现需求,既然这样那为什么已经有了reactive还需要再去设计一个ref呢?这两者的实际运用场景以及区别是什么呢?

并且关于ref的底层逻辑,有的人说ref的底层逻辑还是reactive。有的人说ref的底层是classvalue只是这个class的一个属性,那这两种说法哪种正确呢?都有没有依据呢?

抱着这样的疑问我们本次就深入源码,彻底搞清vue3中reactiveref的区别。(学习视频分享:vue视频教程)

不想看源码的童鞋,可以直接拉到后面看总结

reactive

源码地址:packages/reactivity/reactive.ts

首先我们看一下vue3中用来标记目标对象target类型的ReactiveFlags

// 标记目标对象 target 类型的 ReactiveFlagsexport const enum ReactiveFlags {  SKIP = '__v_skip',  IS_REACTIVE = '__v_isReactive',  IS_READONLY = '__v_isReadonly',  RAW = '__v_raw'}export interface Target {  [ReactiveFlags.SKIP]?: boolean          // 不做响应式处理的数据  [ReactiveFlags.IS_REACTIVE]?: boolean   // target 是否是响应式  [ReactiveFlags.IS_READONLY]?: boolean   // target 是否是只读  [ReactiveFlags.RAW]?: any               // 表示proxy 对应的源数据, target 已经是 proxy 对象时会有该属性}
登录后复制

reactive

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}
登录后复制

reactive函数接收一个target对象,如果target对象只读则直接返回该对象

若非只读则直接通过createReactiveObject创建observe对象

createReactiveObject

看着长不要怕,先贴createReactiveObject完整代码,我们分段阅读

/** *  * @param target 目标对象 * @param isReadonly 是否只读 * @param baseHandlers 基本类型的 handlers * @param collectionHandlers 主要针对(set、map、weakSet、weakMap)的 handlers * @param proxyMap  WeakMap数据结构 * @returns  */function createReactiveObject(  target: Target,  isReadonly: boolean,  baseHandlers: ProxyHandler<any>,  collectionHandlers: ProxyHandler<any>,  proxyMap: WeakMap<Target, any>) {  // typeof 不是 object 类型的,在开发模式抛出警告,生产环境直接返回目标对象  if (!isObject(target)) {    if (__DEV__) {      console.warn(`value cannot be made reactive: ${String(target)}`)    }    return target  }  // target is already a Proxy, return it.  // exception: calling readonly() on a reactive object  // 已经是响应式的就直接返回(取ReactiveFlags.RAW 属性会返回true,因为进行reactive的过程中会用weakMap进行保存,  // 通过target能判断出是否有ReactiveFlags.RAW属性)  // 例外:对reactive对象进行readonly()  if (    target[ReactiveFlags.RAW] &&    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])  ) {    return target  }  // target already has corresponding Proxy  // 对已经Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象  const existingProxy = proxyMap.get(target)  if (existingProxy) {    return existingProxy  }  // only a whitelist of value types can be observed.  // 只对targetTypeMap类型白名单中的类型进行响应式处理  const targetType = getTargetType(target)  if (targetType === TargetType.INVALID) {    return target  }  // proxy 代理 target  // (set、map、weakSet、weakMap) collectionHandlers  // (Object、Array) baseHandlers  const proxy = new Proxy(    target,    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers  )  proxyMap.set(target, proxy)  return proxy}
登录后复制

首先我们看到createReactiveObject接收了五个参数

  target: Target,  isReadonly: boolean,  baseHandlers: ProxyHandler<any>,  collectionHandlers: ProxyHandler<any>,  proxyMap: WeakMap<Target, any>
登录后复制

target 目标对象

isReadonly 是否只读

baseHandlers 基本类型的 handlers 处理数组,对象

collectionHandlers 处理 set、map、weakSet、weakMap

proxyMap WeakMap数据结构存储副作用函数


这里主要是通过ReactiveFlags.RAWReactiveFlags.IS_REACTIVE判断是否是响应式数据,若是则直接返回该对象

 if (    target[ReactiveFlags.RAW] &&    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])  ) {    return target  }
登录后复制

对于已经是Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象并返回

  const existingProxy = proxyMap.get(target)  if (existingProxy) {    return existingProxy  }
登录后复制

这里则是校验了一下当前target的类型是不是ObjectArrayMapSetWeakMapWeakSet,如果都不是则直接返回该对象,不做响应式处理

 // 只对targetTypeMap类型白名单中的类型进行响应式处理  const targetType = getTargetType(target)  if (targetType === TargetType.INVALID) {    return target  }
登录后复制

校验类型的逻辑

function getTargetType(value: Target) {  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)    ? TargetType.INVALID    : targetTypeMap(toRawType(value))}function targetTypeMap(rawType: string) {  switch (rawType) {    case 'Object':    case 'Array':      return TargetType.COMMON    case 'Map':    case 'Set':    case 'WeakMap':    case 'WeakSet':      return TargetType.COLLECTION    default:      return TargetType.INVALID  }}
登录后复制

所有的前置校验完后,就可以使用proxy 代理target对象了

这里使用了一个三目运算符通过TargetType.COLLECTION来执行不同的处理逻辑

(set、map、weakSet、weakMap) 使用 collectionHandlers(Object、Array) 使用 baseHandlers
// proxy 代理 target  // (set、map、weakSet、weakMap) collectionHandlers  // (Object、Array) baseHandlers  const proxy = new Proxy(    target,    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers  )  proxyMap.set(target, proxy)  return proxy
登录后复制

现在对createReactiveObject的执行逻辑是不是就很清晰了

到这里还没有结束,createReactiveObject中最后proxy是如何去代理target的呢?这里我们用baseHandlers举例,深入baseHandlers的内部去看看

baseHandlers

源码地址:packages/reactivity/baseHandlers.ts

reactive.ts中我们可以看到一共引入了四种 handler

import {  mutableHandlers,  readonlyHandlers,  shallowReactiveHandlers,  shallowReadonlyHandlers} from './baseHandlers'
登录后复制mutableHandlers 可变处理readonlyHandlers 只读处理shallowReactiveHandlers 浅观察处理(只观察目标对象的第一层属性)shallowReadonlyHandlers 浅观察 && 只读

我们以mutableHandlers为例

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}0
登录后复制

这里的getset分别对应着createGetter()createSetter()

createGetter()

先上完整版代码

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}1
登录后复制

看着长,最终就是track()依赖收集

track()依赖收集内容过多,和trigger()触发更新一起,单开一篇文章

createSetter()

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}2
登录后复制

trigger()触发更新

ref

源码地址:packages/reactivity/src/ref.ts

接收一个可选unknown,接着直接调用createRef()

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}3
登录后复制

ref的区别就是在调用createRef()时第二个值传的是true

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}4
登录后复制

看一下官方文档上对shallowRef的解释

createRef

通过isRef()判断是否是ref数据,是则直接返回该数据,不是则通过new RefImpl创建ref数据

在创建时会传两个值一个是rawValue(原始值),一个是shallow(是否是浅观察),具体使用场景可看上面refshallowRef的介绍

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}5
登录后复制

isRef()

通过__v_isRef只读属性判断是否是ref数据,此属性会在RefImpl创建ref数据时添加

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}6
登录后复制

RefImpl

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}7
登录后复制

根据RefImpl我们可以看到ref的底层逻辑,如果是对象确实会使用reactive进行处理,并且ref的创建使用的也是RefImpl class实例,value只是RefImpl的属性

在我们访问设置 ref的value值时,也分别是通过getset拦截进行依赖收集派发更新

toReactive

我们来看一下toReactive()这个方法,在RefImpl中创建ref数据时会调用toReactive()方法,这里会先判断传进来的值是不是对象,如果是就用reactive()包裹,否则就返回其本身

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}8
登录后复制

trackRefValue

ref的依赖收集方法

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>export function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果目标对象是一个只读的响应数据,则直接返回目标对象  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  // 创建 observe  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  )}9
登录后复制

triggerRefValue

ref的派发更新方法

/** *  * @param target 目标对象 * @param isReadonly 是否只读 * @param baseHandlers 基本类型的 handlers * @param collectionHandlers 主要针对(set、map、weakSet、weakMap)的 handlers * @param proxyMap  WeakMap数据结构 * @returns  */function createReactiveObject(  target: Target,  isReadonly: boolean,  baseHandlers: ProxyHandler<any>,  collectionHandlers: ProxyHandler<any>,  proxyMap: WeakMap<Target, any>) {  // typeof 不是 object 类型的,在开发模式抛出警告,生产环境直接返回目标对象  if (!isObject(target)) {    if (__DEV__) {      console.warn(`value cannot be made reactive: ${String(target)}`)    }    return target  }  // target is already a Proxy, return it.  // exception: calling readonly() on a reactive object  // 已经是响应式的就直接返回(取ReactiveFlags.RAW 属性会返回true,因为进行reactive的过程中会用weakMap进行保存,  // 通过target能判断出是否有ReactiveFlags.RAW属性)  // 例外:对reactive对象进行readonly()  if (    target[ReactiveFlags.RAW] &&    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])  ) {    return target  }  // target already has corresponding Proxy  // 对已经Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象  const existingProxy = proxyMap.get(target)  if (existingProxy) {    return existingProxy  }  // only a whitelist of value types can be observed.  // 只对targetTypeMap类型白名单中的类型进行响应式处理  const targetType = getTargetType(target)  if (targetType === TargetType.INVALID) {    return target  }  // proxy 代理 target  // (set、map、weakSet、weakMap) collectionHandlers  // (Object、Array) baseHandlers  const proxy = new Proxy(    target,    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers  )  proxyMap.set(target, proxy)  return proxy}0
登录后复制总结

看完reactiveref源码,相信对本文一开始的几个问题也都有了答案,这里也总结了几个问题:

问: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的方式呢?

答:这是因为要解决响应式丢失的问题,举个例子:

/** *  * @param target 目标对象 * @param isReadonly 是否只读 * @param baseHandlers 基本类型的 handlers * @param collectionHandlers 主要针对(set、map、weakSet、weakMap)的 handlers * @param proxyMap  WeakMap数据结构 * @returns  */function createReactiveObject(  target: Target,  isReadonly: boolean,  baseHandlers: ProxyHandler<any>,  collectionHandlers: ProxyHandler<any>,  proxyMap: WeakMap<Target, any>) {  // typeof 不是 object 类型的,在开发模式抛出警告,生产环境直接返回目标对象  if (!isObject(target)) {    if (__DEV__) {      console.warn(`value cannot be made reactive: ${String(target)}`)    }    return target  }  // target is already a Proxy, return it.  // exception: calling readonly() on a reactive object  // 已经是响应式的就直接返回(取ReactiveFlags.RAW 属性会返回true,因为进行reactive的过程中会用weakMap进行保存,  // 通过target能判断出是否有ReactiveFlags.RAW属性)  // 例外:对reactive对象进行readonly()  if (    target[ReactiveFlags.RAW] &&    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])  ) {    return target  }  // target already has corresponding Proxy  // 对已经Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象  const existingProxy = proxyMap.get(target)  if (existingProxy) {    return existingProxy  }  // only a whitelist of value types can be observed.  // 只对targetTypeMap类型白名单中的类型进行响应式处理  const targetType = getTargetType(target)  if (targetType === TargetType.INVALID) {    return target  }  // proxy 代理 target  // (set、map、weakSet、weakMap) collectionHandlers  // (Object、Array) baseHandlers  const proxy = new Proxy(    target,    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers  )  proxyMap.set(target, proxy)  return proxy}1
登录后复制

可以看到,在现在的newObj对象下,具有与obj对象同名的属性,而且每个属性的值都是一个对象,例如foo 属性的值是:

/** *  * @param target 目标对象 * @param isReadonly 是否只读 * @param baseHandlers 基本类型的 handlers * @param collectionHandlers 主要针对(set、map、weakSet、weakMap)的 handlers * @param proxyMap  WeakMap数据结构 * @returns  */function createReactiveObject(  target: Target,  isReadonly: boolean,  baseHandlers: ProxyHandler<any>,  collectionHandlers: ProxyHandler<any>,  proxyMap: WeakMap<Target, any>) {  // typeof 不是 object 类型的,在开发模式抛出警告,生产环境直接返回目标对象  if (!isObject(target)) {    if (__DEV__) {      console.warn(`value cannot be made reactive: ${String(target)}`)    }    return target  }  // target is already a Proxy, return it.  // exception: calling readonly() on a reactive object  // 已经是响应式的就直接返回(取ReactiveFlags.RAW 属性会返回true,因为进行reactive的过程中会用weakMap进行保存,  // 通过target能判断出是否有ReactiveFlags.RAW属性)  // 例外:对reactive对象进行readonly()  if (    target[ReactiveFlags.RAW] &&    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])  ) {    return target  }  // target already has corresponding Proxy  // 对已经Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象  const existingProxy = proxyMap.get(target)  if (existingProxy) {    return existingProxy  }  // only a whitelist of value types can be observed.  // 只对targetTypeMap类型白名单中的类型进行响应式处理  const targetType = getTargetType(target)  if (targetType === TargetType.INVALID) {    return target  }  // proxy 代理 target  // (set、map、weakSet、weakMap) collectionHandlers  // (Object、Array) baseHandlers  const proxy = new Proxy(    target,    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers  )  proxyMap.set(target, proxy)  return proxy}2
登录后复制

该对象有一个访问器属性value,当读取value的值时,最终读取的是响应式数据obj下的同名属性值。也就是说,当在副作用函数内读取newObj.foo时,等价于间接读取了obj.foo的值。这样响应式数据就能够与副作用函数建立响应联系

(学习视频分享:web前端开发、编程基础视频)

以上就是详解vue3中reactive和ref的区别(源码解析)的详细内容,更多请关注9543建站博客其它相关文章!

9543建站博客
一个专注于网站开发、微信开发的技术类纯净博客。
作者头像
admin创始人

肥猫,知名SEO博客站长,14年SEO经验。

上一篇:浅析uniapp $getappmap方法的用法
下一篇:html &gt; 转义

发表评论

关闭广告
关闭广告