响应式基础 API(reactive.ts)
源码调试方法
node
版本要大于 16,装依赖- 执行
dev
命令,比如yarn dev
- 页面中引用
dist/vue.global.js
即可源码本身也有一些
examples html
,比如packages/vue/examples/composition/commits.html
createReactiveObject
创建响应式对象的方法,非常重要
baseHandlers
和collectionHandlers
是调用对应的方法时就确定好的,就是reactive
、readonly
...
proxyMap
也是调用对应的方法时就确定好的,而proxyMap
对应的是以下 4 种tsexport const reactiveMap = new WeakMap<Target, any>(); export const shallowReactiveMap = new WeakMap<Target, any>(); export const readonlyMap = new WeakMap<Target, any>(); export const shallowReadonlyMap = new WeakMap<Target, any>();
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 如果target的类型不属于Object, Array, Map, Set, WeakMap, WeakSet其中的一个,则直接返回
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// 如果target已经是一个由本方法创建的proxy了,直接返回
// 例外: 调用readonly创建reactive对象的只读副本 e.g. readonly(reactive({}))
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target;
}
// 在对应的缓存列表(WeakMap)中寻找对应的Proxy
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target);
// 判断target的类型是否符合要求,TargetType.INVALID代表target不能扩展或者被标记了不能转换为响应式对象(markRaw)
if (targetType === TargetType.INVALID) {
return target;
}
const proxy = new Proxy(
target,
// Map, Set, WeakMap, WeakSet使用collectionHandlers
// Object, Array使用baseHandlers
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
);
// 缓存起来
proxyMap.set(target, proxy);
return proxy;
}
有了createReactiveObject
,下面 4 中创建响应式对象的方法,只要传对应的参数即可
reactive
创建响应式对象并返回副本,响应式转换是“深层”的
如果任何
property
使用了ref
,当它通过代理访问时,则被自动解包如果传入的
target
是一个readonly
代理,则直接返回target
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, // 对应的baseHandlers
mutableCollectionHandlers, // 对应的collectionHandlers
reactiveMap // 对应的proxyMap
);
}
readonly
接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套
property
也是只读的。如果任何 property 使用了
ref
,当它通过代理访问时,则被自动解包
export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers,
readonlyMap
);
}
shallowReactive
返回浅层的响应式对象副本,只有根级别属性是响应的,不会自动展开
ref
(即使是根属性的ref
)
export function shallowReactive<T extends object>(
target: T
): ShallowReactive<T> {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
shallowReactiveMap
);
}
shallowReadonly
返回浅层的响应式对象只读副本,只有根级别属性是响应的,不会自动展开
ref
(即使是根属性的ref
)
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
return createReactiveObject(
target,
true,
shallowReadonlyHandlers,
shallowReadonlyCollectionHandlers,
shallowReadonlyMap
);
}
小结
都是由
createReactiveObject
创建的代理reactive
、readonly
会进行深层的转换,在get
的时候将解包所有深层的 refs,同时维持ref
的响应性。shallowReactive
、shallowReadonly
会进行浅层的转换(即根对象),任何使用ref
的 property 都不会被代理自动解包
ReactiveFlags
从名字就可以看出,这些属性用于判断
响应式
对象的一些标识
export const enum ReactiveFlags {
// 跳过标识
SKIP = "__v_skip",
// 是否为(reactive / shallowReactive)创建的响应式对象
IS_REACTIVE = "__v_isReactive",
// 是否为只读响应式代理(readonly / shallowReadonly)
IS_READONLY = "__v_isReadonly",
// 是否为浅层代理
IS_SHALLOW = "__v_isShallow",
// 原始对象
RAW = "__v_raw",
}
export interface Target {
[ReactiveFlags.SKIP]?: boolean;
[ReactiveFlags.IS_REACTIVE]?: boolean;
[ReactiveFlags.IS_READONLY]?: boolean;
[ReactiveFlags.IS_SHALLOW]?: boolean;
[ReactiveFlags.RAW]?: any;
}
IS_REACTIVE
TIP
其中ReactiveFlags.IS_REACTIVE
比较特殊,他的值取决于target
的isReadonly
。isReadonly
是在创建proxy
的时候就确定好的(具体的实现是在proxy
的handler
中)
拿
baseHandlers.ts
举例
const get = /*#__PURE__*/ createGetter();
const shallowGet = /*#__PURE__*/ createGetter(false, true);
const readonlyGet = /*#__PURE__*/ createGetter(true);
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true);
reactive
、readonly
、shallowReactive
、shallowReadonly
都是创建响应式对象(代理),因此IS_REACTIVE
只需判断只读
的标识即可
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly;
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly;
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow;
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target;
}
// ...
};
}
isReactive
检查对象是否是由
reactive
创建的响应式代理。从源码可以看出,如果是
readonly
创建的value
,会进行递归判断,也就是说
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW]);
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);
}
isReadonly
检查对象是否是由
readonly
创建的只读代理。他的工作就是检查
ReactiveFlags.IS_READONLY
标识
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);
}
isShallow
v3.2.28 (2022-01-21)+
,但本文记录时,官网文档没有说明该方法,算是彩蛋了检查对象是否为浅层的代理
他的工作就是检查
ReactiveFlags.IS_SHALLOW
标识
export function isShallow(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW]);
}
isProxy
检查对象是否是由
reactive
或readonly
创建的 proxy。所以他的判断交给了
isReactive
和isReadonly
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value);
}
toRaw
先拿到 observed 的(ReactiveFlags.RAW)值
如果 raw 没有值(undefined),则证明 observed 是普通对象,直接返回 observed
如果 raw 有值,那么会存在两种情况
- observed 的代理只有一层
- observed 是一个嵌套多层的响应式对象,比如:readonly(reactive({}))、readonly(readonly({}))
所以需要递归判断
重点结束条件:如果 raw 的值是 undefined 就是拿到原始对象了
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW];
return raw ? toRaw(raw) : observed;
}
markRaw
标记一个对象不会被代理,加入黑名单,不会被
observed
。目前来看似乎没有提供方法取消标记用
Object.defineProperty
设置ReactiveFlags.SKIP
属性为true
在用
reactive
创建响应式对象时,会调用getTargetType(target)
进行判断,如果target
的ReactiveFlags.SKIP
属性是true
,或者是不可扩展的,则认定这个target
是无效的;因此并不会创建响应式对象
export function markRaw<T extends object>(value: T): T {
def(value, ReactiveFlags.SKIP, true);
return value;
}
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value));
}
function createReactiveObject() {
// ...
// only a whitelist of value types can be observed.
const targetType = getTargetType(target);
if (targetType === TargetType.INVALID) {
return target; // 返回原始对象
}
// ...
}
例子
const obj: any = {
name: "gauhar",
};
// obj被标记了
markRaw(obj);
// state不是响应的
const state = reactive(obj); // state === obj
function handleClick() {
// 不会响应
state.name = "1234";
state.sex = "man";
}
上面的state === obj
,因为创建reactive
的时候被拦截return target
了。属性有被修改和新增,但已经不是响应式的了。
markRaw
只劫持了一层(从源码可以看出),因此对面里的属性没有被标注。比如const obj: any = {
name: "gauhar",
info: {
hair: "black",
},
};
// obj被标注了,但info没有
markRaw(obj);
const state = reactive(obj);
// 这种情况下如果使用obj.info去创建reactive
const data = reactive(obj.info);
function handleClick() {
// 不会响应(按照我们上面的说法,这里应该是不会响应的)
state.name = "1234";
state.sex = "man";
// 但是由于`同一性风险`,会得到原始对象(obj)被代理后的版本;这个函数里的改变会被响应
data.hair = "red"; // observed
}
关于同一性风险具体可以参考官网 👊
两个内部方法
都是进行对应的转换
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value;
export const toReadonly = <T extends unknown>(value: T): T =>
isObject(value) ? readonly(value as Record<any, any>) : value;