Vue3还有8个响应式工具API有用过吗?

147 阅读8分钟
image.png

上一篇文章通过示例和源码分析介绍了 Vue3的10个响应式进阶API,这篇文章继续通过示例和简要源码分析深入了解Vue3的8个响应式工具API:

1、isRef()

检查某个值是否为 ref。返回值是一个类型判定,这意味着 isRef 可以被用作类型守卫。 ref值和shallowRef值都会带上一个 __v_isRef属性:

const count = ref(1);
console.log(count);

const state = shallowRef({ count: 1 });
console.log(state);

image.png 源码:

// \packages\reactivity\src\ref.ts

export function isRef(r: any): r is Ref {
  return !!(r && r.__v_isRef === true)
}

// ref值和shallowRef值都会带上一个__v_isRef属性
export function ref(value?: unknown) {
  return createRef(value, false) // 传入shallow:false
}

export function shallowRef(value?: unknown) {
  return createRef(value, true) // 传入shallow:true
}

function createRef(rawValue: unknown, shallow: boolean) {
  // 如果已经是一个ref值,直接返回
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  // 默认带有公共只读属性 __v_isRef:true
  public readonly __v_isRef = true
  
  constructor( value: T, public readonly __v_isShallow: boolean) {
   //...
  }
  get value() {
   //...
  }
  set value(newVal) {
   //...
  }
}

2、unref()

如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

源码:

// \packages\reactivity\src\ref.ts

export function unref<T>(ref: MaybeRef<T>): T {
  return isRef(ref) ? ref.value : ref
}

3、toRef()

(1)传入一个ref值,直接返回这个ref

const count = ref(1);
const countRef = toRef(count);
console.log(countRef);

const state = shallowRef({ count: 1 });
const stateRef = toRef(state);
console.log(stateRef);

image.png

(2)传入一个getter函数,会创建一个只读的 ref,当访问 .value 时会调用此 getter 函数并返回值(vue3.3+可用)

const props = defineProps({
  count: {
    type: Number,
    default: 1,
  },
  state: {
    type: Object,
    default: () => ({ count: 2 }),
  },
});

const countRef = toRef(() => props.count);
const stateRef = toRef(() => props.state);

console.log(countRef);
// 访问.value 时返回getter函数调用的返回值
console.log(countRef.value);
console.log(stateRef);
// 访问.value 时返回getter函数调用的返回值
console.log(stateRef.value);

image.png

(3)传入普通对象和key值,依然会返回一个ref,访问.value返回这个对象的key值;即使这个key属性,在源对象上不存在,也会返回一个ref

const state = {
  foo: 1,
};

const fooRef = toRef(state, "foo");
const barRef = toRef(state, "bar");

console.log(fooRef);
// 等同于访问state.foo
console.log(fooRef.value);

console.log(barRef);
// 等同于访问state.bar
console.log(barRef.value); // undefined

image.png

(4)传入响应式对象和key值,依然会返回一个ref,访问.value返回这个对象的key值

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

const fooRef = toRef(state, "foo");

console.log(fooRef);
console.log(fooRef.value);

image.png

并且修改属性,会同步:

const state = reactive({
  foo: 1,
});
// state与fooRef会产生联系,下面源码可以很清晰看到
const fooRef = toRef(state, "foo");

state.foo = 2;
// fooRef访问.value,实际上访问的就是state.foo
console.log(fooRef.value); // 2

// 实际上修改的是state.foo
fooRef.value = 3;
console.log(state.foo); //3

但下面这种情况不同

const state = reactive({
  bar: 2,
});
// 相当于 const barRef = ref(2)
// state与barRef没有关系了
const barRef = ref(state.bar); 

state.bar = 4;
console.log(barRef.value); //2
barRef.value = 5;
console.log(state.bar); //4

(5)如果传入props,同样不允许修改,因为props就是用reactive包裹后的响应式对象:

const props = defineProps({
  count: {
    type: Number,
    default: 1,
  },
});

console.log(isReactive(props)); //true

const countRef = toRef(props, "count");
console.log(countRef);

// 等同于 props.count = 3
countRef.value = 3;
// 访问.value 等同于访问 props.count
console.log(countRef.value); // 1

image.png

(6)如果传入基本数据类型,就直接 return ref(X)

// 等同于 const state = ref(1)
const state = toRef(1);

console.log(state);

image.png

源码:

// \packages\reactivity\src\ref.ts

export function toRef(
  source: Record<string, any> | MaybeRef,
  key?: string,
  defaultValue?: unknown
): Ref {
  if (isRef(source)) {
    // 如果source已经是ref值,直接返回
    return source
  } else if (isFunction(source)) {
    // 如果source是一个函数
    return new GetterRefImpl(source) as any
  } else if (isObject(source) && arguments.length > 1) {
    // 如果source是对象,并且传入了key
    return propertyToRef(source, key!, defaultValue)
  } else {
    // source是一个基本数据类型
    return ref(source)
  }
}

class GetterRefImpl<T> {
  public readonly __v_isRef = true
  public readonly __v_isReadonly = true
  // 传入一个getter函数
  constructor(private readonly _getter: () => T) {}
  get value() {
    // 当访问 .value 时会调用此 getter 函数并返回
    return this._getter()
  }
}

// 当source是一个对象时调用
function propertyToRef(
  source: Record<string, any>,
  key: string,
  defaultValue?: unknown
) {
  const val = source[key]
  return isRef(val)
    ? val // 如果得到一个ref直接返回
    : (new ObjectRefImpl(source, key, defaultValue) as any)
}

// 当source是一个除ref对象外的其他对象
class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true
  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}
  
  // 这个get和set就解释了把一个reactive对象放到toRef里面,
  // 修改会同步
  get value() {
    // 获取的就是源对象的属性值
    const val = this._object[this._key]
    return val === undefined ? this._defaultValue! : val
  }
  set value(newVal) {
   // 修改的就是源对象
   // 所以把props放到toRef依然不能修改,因为修改的就是props
    this._object[this._key] = newVal
  }
}

4、toValue()(Vue3.3+可用)

将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。

这可以在组合式函数中使用,用来规范化一个可以是值、ref 或 getter 的参数。

toValue(1) // --> 1
toValue(ref(1)) // --> 1 
toValue(() => 1) // --> 1
import type { MaybeRefOrGetter } from "vue";

function useFeature(id: MaybeRefOrGetter<number>) {
  watch(
    () => toValue(id),
    (id) => {
      // 处理 id 变更
    }
  );
}

// 这个组合式函数支持以下的任意形式:
useFeature(1);
useFeature(ref(1));
useFeature(() => 1);

源码:

// \packages\reactivity\src\ref.ts

export function toValue<T>(source: MaybeRefOrGetter<T>): T {
 // 很简单,是函数就调用返回,不是就用unref()
  return isFunction(source) ? source() : unref(source)
}

export function unref<T>(ref: MaybeRef<T>): T {
  return isRef(ref) ? ref.value : ref
}

5、toRefs()

(1)应该传入一个由 reactive()readonly()shallowReactive() 或 shallowReadonly()创建的代理对象(不能传入ref()shallowRef()创建的ref对象),相当于把每个属性变成ref对象,可以解构,依然不失去响应式

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

const stateAsRefs = toRefs(state);
console.log(stateAsRefs);

const { foo, bar } = stateAsRefs;
console.log(foo, bar);

state.foo = 3;
console.log(foo.value); // 3

bar.value = 4;
console.log(state.bar); //4

image.png

(2)同样也能传入reactive数组解构出ref对象:

const arr = reactive([1, 2, 3]);

const [a, b, c] = toRefs(arr);

console.log(a, b, c);

image.png

(3)同样,把props里面的属性解构为ref对象,也不能修改,道理和toRef一样

const props = defineProps({
  foo: {
    type: Number,
    default: 1,
  },
});

const { foo } = toRefs(props);
console.log(foo);

foo.value = 2;

console.log(foo.value); // 1

image.png

当从组合式函数中返回响应式对象时,toRefs 相当有用。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性:

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

  // ...基于状态的操作逻辑

  // 在返回时都转为 ref
  return toRefs(state);
}

// 可以解构而不会失去响应性
const { foo, bar } = useFeatureX();

源码:

// /vue-3.3.7/packages/reactivity/src/ref.ts

export function toRefs<T extends object>(object: T): ToRefs<T> {
  // 创建一个数组或对象
  const ret: any = isArray(object) ? new Array(object.length) : {}
  // for in只会为源对象上可以枚举的属性创建 ref对象
  // 如果要为可能还不存在的属性创建 ref,那就用toRef
  for (const key in object) {
    ret[key] = propertyToRef(object, key)
  }
  return ret
}

function propertyToRef(
  source: Record<string, any>,
  key: string,
  defaultValue?: unknown
) {
  const val = source[key]
  return isRef(val)
    ? val // 是ref直接返回
    : (new ObjectRefImpl(source, key, defaultValue) as any)
}

class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true

  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}
  // get 和set 同 toRef逻辑一样
  get value() {
    const val = this._object[this._key]
    return val === undefined ? this._defaultValue! : val
  }
  set value(newVal) {
    this._object[this._key] = newVal
  }

}

6、isReadonly

检查传入的值是否为只读对象。 通过 readonly() 和 shallowReadonly() 创建的代理都是只读的, readonly() 对任何嵌套属性的访问都将是只读的,shallowReadonly()只有根层级的属性变为了只读

源码:

// vue-3.3.7/packages/reactivity/src/reactive.ts

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}


export const enum ReactiveFlags {
  IS_READONLY = '__v_isReadonly'
}

// 通过 `readonly()` 和 `shallowReadonly()` 创建代理对象,
// 都会通过调用createReactiveObject()函数调用
// 只是传入的第3、4个参数不同
export function readonly<T extends object>(target: T){
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers,
  )
}

export function shallowReadonly<T extends object>(target: T){
  return createReactiveObject(
    target,
    true,
    shallowReadonlyHandlers,
    shallowReadonlyCollectionHandlers,
  )
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  const proxy = new Proxy(
    target,
    // 'Object''Array'执行baseHandler ,
    // Map Set WeakMap WeakSet执行collectionHandler
    targetType === TargetType.COLLECTION ?
                   collectionHandlers : 
                   baseHandlers
  );
  return proxy;
}

// createReactiveObject的第3、4个参数都是作为proxy的handler
export const readonlyHandlers = new ReadonlyReactiveHandler()
export const shallowReadonlyHandlers = new ReadonlyReactiveHandler(true)
export const readonlyCollectionHandlers = {
  get: createInstrumentationGetter(true, false)
}
export const shallowReadonlyCollectionHandlers = {
  get: createInstrumentationGetter(true, true)
}

// 先看ReadonlyReactiveHandler怎么实现
// isReadonly通过访问key值ReactiveFlags.IS_READONLY
// 进入BaseReactiveHandler的get方法中
class ReadonlyReactiveHandler extends BaseReactiveHandler {
  constructor(shallow = false) {
    super(true, shallow);
  }
  // 只读,不能设置
  set(target: object, key: string | symbol) {
    if (__DEV__) {
      warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target)
    }
    return true
  }
}

class BaseReactiveHandler {
  constructor(
    protected readonly _isReadonly = false,
  ) {}
  
  get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly; 
    // 通过 readonly和 shallowReadonly创建代理对象会返回true
    if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly;  // true
    }
  }
}

// 再看 createInstrumentationGetter
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  return (target: CollectionTypes, key: string | symbol) => {
   // 通过 readonly和 shallowReadonly创建代理对象会返回true
    if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly; // true
    }
  };
}

7、isReactive

检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。

isReactive(reactive({}))            // => true
isReactive(readonly(reactive({})))  // => true
isReactive(ref({}).value)           // => true
isReactive(readonly(ref({})).value) // => true
isReactive(ref(true))               // => false
isReactive(shallowRef({}).value)    // => false
isReactive(shallowReactive({}))     // => true

源码:

export function isReactive(value: unknown): boolean {
  // 当value是只读的,但如果传入readonly(或shallowReadonly)
  // 的是reactive(或shallowReactive)对象
  // 依然返回true
  if (isReadonly(value)) {
    //如: isReactive(readonly(reactive({})))  // => true
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

// 同readonly一样,reactive和shallowReactive也通过createReactiveObject创建
export function reactive(target) {
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

export function shallowReactive(target){
  return createReactiveObject(
    target,
    false,
    shallowReactiveHandlers,
    shallowCollectionHandlers,
    shallowReactiveMap
  )
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  const proxy = new Proxy(
    target,
    // 'Object''Array'执行baseHandler ,
    // Map Set WeakMap WeakSet执行collectionHandler
    targetType === TargetType.COLLECTION ?
                   collectionHandlers : 
                   baseHandlers
  );
  // 缓存 target proxy
  proxyMap.set(target, proxy) 
  return proxy;
}

export const mutableHandlers = new MutableReactiveHandler()
export const shallowReactiveHandlers = new MutableReactiveHandler(true)

class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(shallow = false) {
    super(false, shallow)
  }
  //...
}

class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false, 
    protected readonly _shallow = false
  ) {}

  get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly;  // false
    const shallow = this._shallow;  //false
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly;  // true
    } else if (
     // 当进入这个条件,说明传入isReactive的value是只读
     // 当这个条件为true的情况是传入readonly(或shallowReadonly)
     // 的是reactive(或shallowReactive)对象
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target;
    }
  }
}

8、isProxy

检查一个对象是否是由 reactive()readonly()shallowReactive()或 shallowReadonly() 创建的代理。相当于是isReadonlyisReactive的并集

源码:

// vue-3.3.7/packages/reactivity/src/reactive.ts

export function isProxy(value: unknown): boolean {
  return isReactive(value) || isReadonly(value)
}

姊妹篇

Vue3的10个响应式进阶API有用过吗?

参考

官网API