前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue的ref和reactive的区别-源码解读

Vue的ref和reactive的区别-源码解读

作者头像
韦东锏
发布2024-05-30 20:33:41
680
发布2024-05-30 20:33:41
举报
文章被收录于专栏:Android码农Android码农

引子

在看vueuse官方文档的时候,有这么一段话 Use ref instead of reactive whenever possible

所以想从源码角度去看下,两者的差别,为什么官方要这么说

结论

先说结论

  • ref可以对基本数据类型保持响应式,reactive只能对对象,数组保持响应式
  • ref的取值要用.value
  • reactive的内部原理使用proxy实现的
  • ref如果传的是非基本数据类型,内部其实也是转成reactive,无本质区别

ref源码

ref的源码路径:packages/reactivity/src/ref.ts 先看一个使用代码

代码语言:javascript
复制
import { ref } from 'vue';
const count = ref(0);

上面的代码中,引入的ref其实是一个方法

代码语言:javascript
复制
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value, false)
}

调用了内部的createRef方法,第二个参数是shallow,代表是否是浅层次的响应式,false代表是深层次的响应,比如传的是对象,对象内部的属性都会有响应式

如果用的是shallowRef,这个值就是true

代码语言:javascript
复制
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}

继续看下createRef

代码语言:javascript
复制
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

这里有个判断,如果已经是ref对象,就直接返回,避免重复ref

代码语言:javascript
复制
class RefImpl<T> {
  private _value: T
  private _rawValue: T
 
 
  public dep?: Dep = undefined
  public readonly __v_isRef = true
 
  constructor(
    value: T,
    public readonly __v_isShallow: boolean,
  ) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }
 
  get value() {
    trackRefValue(this)
    return this._value
  }
 
  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, DirtyLevels.Dirty, newVal)
    }
  }
}
 
 
export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

RefImpl是一个class,有一个_rawValue,保存原始value,另外有个value,如果是对象,会转成reactive,跟直接用reactive没本质区别,不是的话,就是原始value 另外RefImpl还有一个value的get和set方法,所以我们用ref都要用.value的原因

代码语言:javascript
复制
get value() {
  trackRefValue(this)
  return this._value
}
 
 
export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    trackEffect(
      activeEffect,
      (ref.dep ??= createDep(
        () => (ref.dep = undefined),
        ref instanceof ComputedRefImpl ? ref : undefined,
      )),
      __DEV__
        ? {
            target: ref,
            type: TrackOpTypes.GET,
            key: 'value',
          }
        : void 0,
    )
  }
}
 
 
export const createDep = (
  cleanup: () => void,
  computed?: ComputedRefImpl<any>,
): Dep => {
  const dep = new Map() as Dep
  dep.cleanup = cleanup
  dep.computed = computed
  return dep
}

在value的get方法中,trackRefValue方法就是集成响应式绑定关系,activeEffect就是响应式副作用函数,createDep返回的是一个map对象,用于保存响应式信息

代码语言:javascript
复制
export function trackEffect(
  effect: ReactiveEffect,
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
  if (dep.get(effect) !== effect._trackId) {
    dep.set(effect, effect._trackId)
    const oldDep = effect.deps[effect._depsLength]
    if (oldDep !== dep) {
      if (oldDep) {
        cleanupDepEffect(oldDep, effect)
      }
      effect.deps[effect._depsLength++] = dep
    } else {
      effect._depsLength++
    }
    if (__DEV__) {
      effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!))
    }
  }
}

响应式函数,也会有一个deps数组,里面保存这被哪些对象的响应式引用,get保存的副作用函数,是为了在set中触发

代码语言:javascript
复制
set value(newVal) {
  const useDirectValue =
    this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
  newVal = useDirectValue ? newVal : toRaw(newVal)
  if (hasChanged(newVal, this._rawValue)) {
    this._rawValue = newVal
    this._value = useDirectValue ? newVal : toReactive(newVal)
    triggerRefValue(this, DirtyLevels.Dirty, newVal)
  }
}
 
 
export function triggerRefValue(
  ref: RefBase<any>,
  dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
  newVal?: any,
) {
  ref = toRaw(ref)
  const dep = ref.dep
  if (dep) {
    triggerEffects(
      dep,
      dirtyLevel,
      __DEV__
        ? {
            target: ref,
            type: TriggerOpTypes.SET,
            key: 'value',
            newValue: newVal,
          }
        : void 0,
    )
  }
}

可以看到,只有新的内容跟原有内容不一样,才会触发响应式,响应式就是把副作用函数拿出来执行一下

reactive的源码

源码路径:packages/reactivity/src/reactive.ts 上面知道,ref的如果传的是对象,最终也是转成reactive,接下来看下reactive的实现,如何实现响应式

先看下reactive的使用例子

代码语言:javascript
复制
import { reactive } from 'vue';
const state = reactive({ count: 0 });
// 访问
console.log(state.count); // 0
// 更新
state.count = 1;

其实reactive也是一个方法

代码语言:javascript
复制
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 (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}

createReactiveObject方法,其实返回的是一个proxy

代码语言:javascript
复制
export const reactiveMap = new WeakMap<Target, any>()
 
 
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  if (!isObject(target)) {
    if (__DEV__) {
      warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )
  proxyMap.set(target, proxy)
  return proxy
}

reactiveMap是缓存所有的proxy,下次重新reactive避免重复生成proxy,由于是weakmap,再target被回收后,对应的proxy也会被自动回收

proxy都有一个handler,源码针对数组跟对象,是两个不同的handler处理,我们这里只看下对象的场景,就是baseHandlers,也就是方法传参的mutableHandlers

代码语言:javascript
复制
export const mutableHandlers: ProxyHandler<object> =
  /*#__PURE__*/ new MutableReactiveHandler()
 
class MutableReactiveHandler extends BaseReactiveHandler{}
 
 
 
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _isShallow = false,
  ) {}
 
 
  get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly,
      isShallow = this._isShallow
 
    const res = Reflect.get(target, key, receiver)
 
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
 
    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }
 
    return res
  }
}

跟ref一样的,也是在get方法中,由track方法跟副作用函数绑定响应式,另外这个返回的如果是一个对象,返回是一个reactive对象,如果是基本类型,就直接返回基本类型

代码语言:javascript
复制
const targetMap = new WeakMap<object, KeyToDepMap>()
 
 
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep(() => depsMap!.delete(key))))
    }
    trackEffect(
      activeEffect,
      dep,
      __DEV__
        ? {
            target,
            type,
            key,
          }
        : void 0,
    )
  }
}

targetMap用于全局存储全部的响应式数据,key就是对象,value也是一个Map数组

在这个map数组中,key是对象的某个属性字段,value是副作用函数,这样副作用函数是跟对象的某个字段绑定,而不是跟整个对象绑定

接下来看下set方法

代码语言:javascript
复制
class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(isShallow = false) {
    super(false, isShallow)
  }
 
 
  set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
    let oldValue = (target as any)[key]
     
 
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
 
}

调用trigger方法,执行响应式

代码语言:javascript
复制
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>,
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
 
 
  let deps: (Dep | undefined)[] = []
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }
  }
 
  pauseScheduling()
  for (const dep of deps) {
    if (dep) {
      triggerEffects(
        dep,
        DirtyLevels.Dirty,
        __DEV__
          ? {
              target,
              type,
              key,
              newValue,
              oldValue,
              oldTarget,
            }
          : void 0,
      )
    }
  }
  resetScheduling()
}

triggerEffects就是从dep数组中,获取全部的响应式副作用函数,一个个调用执行

后记

现在看起来,vueUse官网的这个说法是不对的,没必要所有场景都用ref,因为在代码层面,用ref,都需要用.value,增加的复杂度,确实没必要,比如下面的例子

代码语言:javascript
复制
import { ref } from 'vue';
 
// Using `ref` to hold an object
const userProfile = ref({
  name: 'John Doe',
  age: 30
});
 
function updateProfile() {
  // Access and modify the object through `.value`
  userProfile.value.name = 'Jane Doe';
  userProfile.value.age = 28;
}

所以我的结论是 如果是基础类型,就用ref,其他类型,统一用reactive

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-05-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引子
  • 结论
  • ref源码
  • reactive的源码
  • 后记
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档