vue 响应式 API&原理

178 阅读3分钟

摘要:对以下API的使用和源码进行解析

  • reactive
  • ref
  • computed
  • toRef
  • toRefs
  • watch[todo]
  • watchEffect[todo]

reactive

创建一个响应式对象

const obj = reactive({ count: 0 })
obj.count++

原理: 基于proxy拦截对对象属性的访问和赋值

// 创建一个响应式对象
export function shallowReactive<T extends object>(
  target: T,
): ShallowReactive<T> {
  return createReactiveObject(
    target,
    false,
    shallowReactiveHandlers,
    shallowCollectionHandlers,
    shallowReactiveMap,
  )
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 创建一个Proxy对象对数据进行劫持,进行依赖收集的track,和修改时候的统一响应式触发trigger;
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )
  proxyMap.set(target, proxy)
  return proxy
}

ref

接受基本类型和对象, 创建一个响应式对象,

const count = ref(0)
console.log(count.value) // 0

基础类型基于对对象的value属性访问和设置,建立响应式;对象转换为reactive创建响应式对象

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true
  // 创建构造函数,对value的访问和赋值进行拦截;
  constructor(
    value: T,
    public readonly __v_isShallow: boolean,
  ) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    // 如果value是复杂类型,会转换为reactive来建立响应式;
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    // 访问ref创建的响应式对象,依赖收集
    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)) {
      const oldVal = this._rawValue
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      // 重新赋值时,触发;
      triggerRefValue(this, DirtyLevels.Dirty, newVal, oldVal)
    }
  }
}

computed

可以创建一个只读的ref或者创建一个可写的计算属性 ref

// 可以创建一个只读的ref
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2

// 创建一个可写的计算属性 ref:
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

如果computed传入的是函数,就只读;如果是对象就初始化ComputedRefImpl来做响应式

export function computed(getterOrOptions) {
  let getter;
  let setter;

  if (typeof getterOrOptions === 'function') {
    getter = getterOrOptions;
    setter = () => {
      console.warn('Write operation failed: computed value is readonly');
    };
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }

  const cRef = new ComputedRefImpl(getter, setter, typeof getterOrOptions === 'function' || !getterOrOptions.set);

  return cRef;
}

ComputedRefImpl的实现

class ComputedRefImpl<T> {
  private _value!: T;
  private _dirty = true;
  public dep?: Dep = undefined;
  public readonly effect: ReactiveEffect<T>;

  constructor(getter: ComputedGetter<T>, private readonly _setter: ComputedSetter<T>) {
  // 响应式,对计算属性依赖的值进行收集;
  this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true;
        triggerRefValue(this);
      }
    });
    this.effect.computed = this;
    this.effect.active = true;
  }

  get value() {
    // 如果需要重新计算或依赖项发生变化,则重新计算值,computed做缓存处理;
    if (this._dirty) {
      this._value = this.effect.run();
      this._dirty = false;
    }
    trackRefValue(this);
    return this._value;
  }

  set value(newValue: T) {
    this._setter(newValue);
  }
}

toRef

使用

// 可以把props上的一个属性转换为ref,然后传入
const props = defineProps(/* ... */)

// 将 `props.foo` 转换为 ref,然后传入
// 一个组合式函数
useSomeFeature(toRef(props, 'foo'))

原理

export function toRef(
  source: Record<string, any> | MaybeRef,
  key?: string,
  defaultValue?: unknown,
): Ref {
  // 如果source本身就是ref,就返回原值
  if (isRef(source)) {
    return source
  } else if (isFunction(source)) {
    return new GetterRefImpl(source) as any
  // 如果是对象,就将该属性转为ref;
  } else if (isObject(source) && arguments.length > 1) {
    return propertyToRef(source, key!, defaultValue)
  } else {
    return ref(source)
  }
}

toRefs

使用:将对象每一个属性都转为ref, 可以解构对象并不失去响应式

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

原理

export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    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
}

参考

响应式 API:核心 | Vue.js (vuejs.org)