reactive、readonly 和 ref 的区别和使用场景

1,319 阅读3分钟

reactive API

function reactive (target) {
  // 已经是一个 readonly proxy ,直接返回这个 只读响应式 proxy
  if (target && target.__v_isReadonly) {
     return target
  } 
  return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers)
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  //必须是对象或数组类型,返回target
  if (!isObject(target)) {
    return target
  }
  // target 已经是 Proxy 对象,直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // 查询readonlyMap和reactiveMap,是否存在其中。(所有的reactive和readonly都存在这两个里面)
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  // 只能是指定的数据类型
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 根据对应的数据类型,使用不同的配置
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 放到对应的Map中
  proxyMap.set(target, proxy)
  return proxy
}

reactive创建流程

  1. 已经是一个 readonly proxy ,直接返回这个 proxy
  2. 必须是对象或数组类型,否则返回 target
  3. target 已经是 Proxy 对象,直接返回 Proxy。
  4. 查询readonlyMap和reactiveMap,是否存在其中。直接返回这个 proxy
  5. 只能是指定的数据类型,否则返回 target。Object、Array是1,Map、Set、WeakMap、WeakSet是2。去除了RegExp和Date
  6. 根据对应的数据类型,使用不同的配置。最后放到对应的Map中

readonly API

export function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers
  )
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
...
 其他类型不看,就看ObjectArray的。执行readonlyHandlers
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
...
}

readonlyHandlers

export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  set(target, key) {
    if (__DEV__) {
      console.warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      console.warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}

这里可以看到set和delete操作直接返回true。不对属性做任何修改操作,还报警告。

readonlyGet

const readonlyGet = createGetter(true)

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
  
    ...
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
    
    ...
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
  1. readonly则不需要依赖收集 ,无法修改
  2. 如果属性有对象、数组的话,深度遍历全部修改为只读

ref API

reactive API 对传入的 target 类型有限制,必须是Object或者Array。基础类型(比如 StringNumberBoolean)是不支持的

想把基础数据类型变成响应式,要封装成一个对象太麻烦了。就有了ref API

const a = ref({value:1}) 
const b = shallowRef({}) 

a.value = '1111'
export function ref(value?: unknown) {
  return createRef(value)
}

//判断一个变量是否是ref
function isRef(r: any): r is Ref {
  return Boolean(r && r.__v_isRef === true)
}

function createRef(rawValue: unknown, shallow = false) {
  //已经是ref,直接返回rawValue
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  //私有变量_value,用来保存参数
  private _value: T
  //公用只读变量__v_isRef,标记对象是一个ref
  public readonly __v_isRef = true

  constructor(private _rawValue: T, public readonly _shallow = false) {
    // 如果是对象或者数组类型,则转换一个 reactive 对象
    this._value = _shallow ? _rawValue : isObject(val) ? reactive(val) : val
  }
  
  //访问value属性时,触发track 做依赖收集然后返回它的值
  get value() {
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }
  
  //修改value属性时,触发trigger 函数派发通知,修改_value
  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}
  1. ref返回一个Object
  2. 如果 rawValue 已经是一个ref,返回 rawValue
  3. 如果 rawValue 是一个Object,对 rawValue 进行reactive操作
  4. 使用 shallowRef ,不对 rawValue 进行reactive操作

细说说ref

ref 与 reactive 不同的是。 reactive返回的是一个proxy,ref返回的是一个对象

const a = ref(1);
{
  //标记是否是isRef
  __v_isRef: true
  //私有变量,用来存储传入的实参
  _rawValue: 1
  _shallow: false
  //私有变量,用来存储传入的实参
  _value: 1
  //访问的value值
  __proto__: {
    value: 1
  }
}

_rawValue和_value的区别

_rawValue保存传入实参
_value如果传入的是对象,且使用的是ref而不是shallowRef。_value 保存的就是 reactive 的 Proxy

__v_isRef

const a = ref({ __v_isRef: true, a: 1 });

如果你这么创建一个ref,那么会直接返回这个 target不进行任何操作。
reactivereadonlyref
参数Object、Array、Map、Set、WeakMap、WeakSetObject、Array、Map、Set、WeakMap、WeakSet任意值
只读可修改只读可修改
返回值ProxyProxyObject

reactiveref 区别就在于参数的属性,reactive只能使用ObjectArrayMapSetWeakMapWeakSet,而ref可以使用任意值而且如果参数是Object也会对参数进行reactive