上一篇文章通过示例和源码分析介绍了 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);
源码:
// \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);
(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);
(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
(4)传入响应式对象和key值,依然会返回一个ref,访问.value返回这个对象的key值
const state = reactive({
foo: 1,
});
const fooRef = toRef(state, "foo");
console.log(fooRef);
console.log(fooRef.value);
并且修改属性,会同步:
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
(6)如果传入基本数据类型,就直接 return ref(X)
// 等同于 const state = ref(1)
const state = toRef(1);
console.log(state);
源码:
// \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
(2)同样也能传入reactive数组
解构出ref对象:
const arr = reactive([1, 2, 3]);
const [a, b, c] = toRefs(arr);
console.log(a, b, c);
(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
当从组合式函数中返回响应式对象时,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()
创建的代理。相当于是isReadonly
和isReactive
的并集
源码:
// vue-3.3.7/packages/reactivity/src/reactive.ts
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}