From 9fda9411ec92dc703288b229106116dd0f16e5e7 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 18 Jan 2022 09:17:22 +0800 Subject: [PATCH] feat(reactivity): add isShallow api --- packages/reactivity/__tests__/ref.spec.ts | 5 +++++ .../reactivity/__tests__/shallowReactive.spec.ts | 13 ++++++++++++- packages/reactivity/src/baseHandlers.ts | 2 ++ packages/reactivity/src/index.ts | 1 + packages/reactivity/src/reactive.ts | 8 +++++++- packages/reactivity/src/ref.ts | 14 +++++--------- packages/runtime-core/src/apiWatch.ts | 3 ++- packages/runtime-core/src/customFormatter.ts | 7 ++++--- packages/runtime-core/src/index.ts | 1 + 9 files changed, 39 insertions(+), 15 deletions(-) diff --git a/packages/reactivity/__tests__/ref.spec.ts b/packages/reactivity/__tests__/ref.spec.ts index 0db801a5a42..2451c43ab45 100644 --- a/packages/reactivity/__tests__/ref.spec.ts +++ b/packages/reactivity/__tests__/ref.spec.ts @@ -10,6 +10,7 @@ import { } from '../src/index' import { computed } from '@vue/runtime-dom' import { shallowRef, unref, customRef, triggerRef } from '../src/ref' +import { isShallow } from '../src/reactive' describe('reactivity/ref', () => { it('should hold a value', () => { @@ -227,6 +228,10 @@ describe('reactivity/ref', () => { expect(dummy).toBe(2) }) + test('shallowRef isShallow', () => { + expect(isShallow(shallowRef({ a: 1 }))).toBe(true) + }) + test('isRef', () => { expect(isRef(ref(1))).toBe(true) expect(isRef(computed(() => 1))).toBe(true) diff --git a/packages/reactivity/__tests__/shallowReactive.spec.ts b/packages/reactivity/__tests__/shallowReactive.spec.ts index cfd870dc002..f7c7e07b055 100644 --- a/packages/reactivity/__tests__/shallowReactive.spec.ts +++ b/packages/reactivity/__tests__/shallowReactive.spec.ts @@ -1,4 +1,10 @@ -import { isReactive, reactive, shallowReactive } from '../src/reactive' +import { + isReactive, + isShallow, + reactive, + shallowReactive, + shallowReadonly +} from '../src/reactive' import { effect } from '../src/effect' @@ -24,6 +30,11 @@ describe('shallowReactive', () => { expect(isReactive(reactiveProxy.foo)).toBe(true) }) + test('isShallow', () => { + expect(isShallow(shallowReactive({}))).toBe(true) + expect(isShallow(shallowReadonly({}))).toBe(true) + }) + describe('collections', () => { test('should be reactive', () => { const shallowSet = shallowReactive(new Set()) diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index 0fa1c4bc2ea..3d2ba661973 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -84,6 +84,8 @@ function createGetter(isReadonly = false, shallow = false) { return !isReadonly } else if (key === ReactiveFlags.IS_READONLY) { return isReadonly + } else if (key === ReactiveFlags.IS_SHALLOW) { + return shallow } else if ( key === ReactiveFlags.RAW && receiver === diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index 676f8598fb3..a7a03b8c573 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -22,6 +22,7 @@ export { readonly, isReactive, isReadonly, + isShallow, isProxy, shallowReactive, shallowReadonly, diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index 4716230e40f..7d7e591fc4a 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -17,6 +17,7 @@ export const enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', IS_READONLY = '__v_isReadonly', + IS_SHALLOW = '__v_isShallow', RAW = '__v_raw' } @@ -24,6 +25,7 @@ export interface Target { [ReactiveFlags.SKIP]?: boolean [ReactiveFlags.IS_REACTIVE]?: boolean [ReactiveFlags.IS_READONLY]?: boolean + [ReactiveFlags.IS_SHALLOW]?: boolean [ReactiveFlags.RAW]?: any } @@ -87,7 +89,7 @@ export type UnwrapNestedRefs = T extends Ref ? T : UnwrapRefSimple export function reactive(target: T): UnwrapNestedRefs export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. - if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { + if (isReadonly(target)) { return target } return createReactiveObject( @@ -226,6 +228,10 @@ export function isReadonly(value: unknown): boolean { return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]) } +export function isShallow(value: unknown): boolean { + return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW]) +} + export function isProxy(value: unknown): boolean { return isReactive(value) || isReadonly(value) } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index c972676ac42..20aa6c0a989 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -16,10 +16,6 @@ export interface Ref { * autocomplete, so we use a private Symbol instead. */ [RefSymbol]: true - /** - * @internal - */ - _shallow?: boolean } type RefBase = { @@ -102,9 +98,9 @@ class RefImpl { public dep?: Dep = undefined public readonly __v_isRef = true - constructor(value: T, public readonly _shallow: boolean) { - this._rawValue = _shallow ? value : toRaw(value) - this._value = _shallow ? value : toReactive(value) + constructor(value: T, public readonly __v_isShallow: boolean) { + this._rawValue = __v_isShallow ? value : toRaw(value) + this._value = __v_isShallow ? value : toReactive(value) } get value() { @@ -113,10 +109,10 @@ class RefImpl { } set value(newVal) { - newVal = this._shallow ? newVal : toRaw(newVal) + newVal = this.__v_isShallow ? newVal : toRaw(newVal) if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal - this._value = this._shallow ? newVal : toReactive(newVal) + this._value = this.__v_isShallow ? newVal : toReactive(newVal) triggerRefValue(this, newVal) } } diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index b26c3ee2388..fd09c7434e3 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -1,5 +1,6 @@ import { isRef, + isShallow, Ref, ComputedRef, ReactiveEffect, @@ -205,7 +206,7 @@ function doWatch( if (isRef(source)) { getter = () => source.value - forceTrigger = !!source._shallow + forceTrigger = isShallow(source) } else if (isReactive(source)) { getter = () => source deep = true diff --git a/packages/runtime-core/src/customFormatter.ts b/packages/runtime-core/src/customFormatter.ts index e628f75bad4..768240feb62 100644 --- a/packages/runtime-core/src/customFormatter.ts +++ b/packages/runtime-core/src/customFormatter.ts @@ -1,5 +1,6 @@ import { isReactive, isReadonly, isRef, Ref, toRaw } from '@vue/reactivity' import { EMPTY_OBJ, extend, isArray, isFunction, isObject } from '@vue/shared' +import { isShallow } from '../../reactivity/src/reactive' import { ComponentInternalInstance, ComponentOptions } from './component' import { ComponentPublicInstance } from './componentPublicInstance' @@ -38,7 +39,7 @@ export function initCustomFormatter() { return [ 'div', {}, - ['span', vueStyle, 'Reactive'], + ['span', vueStyle, isShallow(obj) ? 'ShallowReactive' : 'Reactive'], '<', formatValue(obj), `>${isReadonly(obj) ? ` (readonly)` : ``}` @@ -47,7 +48,7 @@ export function initCustomFormatter() { return [ 'div', {}, - ['span', vueStyle, 'Readonly'], + ['span', vueStyle, isShallow(obj) ? 'ShallowReadonly' : 'Readonly'], '<', formatValue(obj), '>' @@ -181,7 +182,7 @@ export function initCustomFormatter() { } function genRefFlag(v: Ref) { - if (v._shallow) { + if (isShallow(v)) { return `ShallowRef` } if ((v as any).effect) { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 65fc62b3e2f..ad4817d91b9 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -15,6 +15,7 @@ export { isProxy, isReactive, isReadonly, + isShallow, // advanced customRef, triggerRef,