From 13f636677af9f453a297d90e2125ae2b661813b5 Mon Sep 17 00:00:00 2001 From: hubvue Date: Mon, 25 Jul 2022 23:38:11 +0800 Subject: [PATCH 1/4] fix(types): reactive collection type is not as expected (#5954) --- .../src/transforms/ssrTransformComponent.ts | 23 ++-- packages/reactivity/src/collectionHandlers.ts | 35 +++++- packages/reactivity/src/reactive.ts | 29 ++++- test-dts/reactivity.test-d.ts | 115 ++++++++++++++++++ 4 files changed, 185 insertions(+), 17 deletions(-) diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts index df190c768a8..2fa885a2f8b 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -334,20 +334,19 @@ function subTransform( // traverse traverseNode(childRoot, childContext) // merge helpers/components/directives into parent context - ;(['helpers', 'components', 'directives'] as const).forEach(key => { - childContext[key].forEach((value: any, helperKey: any) => { - if (key === 'helpers') { - const parentCount = parentContext.helpers.get(helperKey) - if (parentCount === undefined) { - parentContext.helpers.set(helperKey, value) - } else { - parentContext.helpers.set(helperKey, value + parentCount) - } - } else { - ;(parentContext[key] as any).add(value) - } + ;(['components', 'directives'] as const).forEach(key => { + childContext[key].forEach((value: any) => { + ;(parentContext[key] as any).add(value) }) }) + childContext['helpers'].forEach((value: any, helperKey: any) => { + const parentCount = parentContext.helpers.get(helperKey) + if (parentCount === undefined) { + parentContext.helpers.set(helperKey, value) + } else { + parentContext.helpers.set(helperKey, value + parentCount) + } + }) // imports/hoists are not merged because: // - imports are only used for asset urls and should be consistent between // node/client branches diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index 09365f96021..d5e1975c73d 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -1,8 +1,41 @@ -import { toRaw, ReactiveFlags, toReactive, toReadonly } from './reactive' +import { + toRaw, + ReactiveFlags, + toReactive, + toReadonly, + ReactiveObject, + IsCollectionReactive, + IsCollectionReadonly, + ReadonlyObject +} from './reactive' import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect' import { TrackOpTypes, TriggerOpTypes } from './operations' import { capitalize, hasOwn, hasChanged, toRawType, isMap } from '@vue/shared' +type CollectionUnwrapRefs = + IsCollectionReactive extends true + ? ReactiveObject + : IsCollectionReadonly extends true + ? ReadonlyObject + : V + +declare global { + interface Map { + get>>(this: T, key: K): CollectionUnwrapRefs | undefined + forEach>>(this: T, callbackfn: (value: CollectionUnwrapRefs, key: CollectionUnwrapRefs, map: T) => void, thisArg?: any): void; + } + interface WeakMap { + get>>(this: T, key: K): CollectionUnwrapRefs | undefined + forEach>>(this: T, callbackfn: (value: CollectionUnwrapRefs, key: CollectionUnwrapRefs, map: T) => void, thisArg?: any): void; + } + interface Set { + forEach>>(this: TT, callbackfn: (value1: CollectionUnwrapRefs, value2: CollectionUnwrapRefs, set: TT) => void, thisArg?: any): void; + } + interface WeakSet { + forEach>>(this: TT, callbackfn: (value1: CollectionUnwrapRefs, value2: CollectionUnwrapRefs, set: TT) => void, thisArg?: any): void; + } +} + export type CollectionTypes = IterableCollections | WeakCollections type IterableCollections = Map | Set diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index 1a947499710..312b0ee2fa8 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -6,6 +6,7 @@ import { shallowReadonlyHandlers } from './baseHandlers' import { + CollectionTypes, mutableCollectionHandlers, readonlyCollectionHandlers, shallowCollectionHandlers, @@ -13,6 +14,19 @@ import { } from './collectionHandlers' import type { UnwrapRefSimple, Ref, RawSymbol } from './ref' +export declare const CollectionReactiveMarker: unique symbol +export declare const CollectionReadonlyMarker: unique symbol +export declare const ShallowReactiveMarker: unique symbol + +export type IsCollectionReadonly = T extends { + [CollectionReadonlyMarker]?: true +} ? true : false + +export type IsCollectionReactive = T extends { + [CollectionReactiveMarker]?: true +} ? true : false + + export const enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', @@ -64,6 +78,11 @@ function getTargetType(value: Target) { // only unwrap nested ref export type UnwrapNestedRefs = T extends Ref ? T : UnwrapRefSimple +export type ReactiveObject = + T extends CollectionTypes + ? UnwrapNestedRefs & { [CollectionReactiveMarker]?: true } + : UnwrapNestedRefs + /** * Creates a reactive copy of the original object. * @@ -86,7 +105,7 @@ export type UnwrapNestedRefs = T extends Ref ? T : UnwrapRefSimple * count.value // -> 1 * ``` */ -export function reactive(target: T): UnwrapNestedRefs +export function reactive(target: T): ReactiveObject export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. if (isReadonly(target)) { @@ -101,8 +120,6 @@ export function reactive(target: object) { ) } -export declare const ShallowReactiveMarker: unique symbol - export type ShallowReactive = T & { [ShallowReactiveMarker]?: true } /** @@ -146,13 +163,17 @@ export type DeepReadonly = T extends Builtin ? { readonly [K in keyof T]: DeepReadonly } : Readonly +export type ReadonlyObject = + T extends CollectionTypes + ? DeepReadonly> & { [CollectionReadonlyMarker]?: true } + : DeepReadonly> /** * Creates a readonly copy of the original object. Note the returned copy is not * made reactive, but `readonly` can be called on an already reactive object. */ export function readonly( target: T -): DeepReadonly> { +): ReadonlyObject { return createReactiveObject( target, true, diff --git a/test-dts/reactivity.test-d.ts b/test-dts/reactivity.test-d.ts index 0499c6da287..9aaa574bb3c 100644 --- a/test-dts/reactivity.test-d.ts +++ b/test-dts/reactivity.test-d.ts @@ -60,3 +60,118 @@ describe('shallowReadonly ref unwrap', () => { expectType(r.count.n) r.count.n.value = 123 }) + +describe('collection-type', () => { + it('primitive type as value', () => { + const m = reactive(new Map()) + const s = reactive(new Set()) + m.set('a', 1) + s.add('b') + + expectType>(m) + expectType>(s) + expectType(m.get('a')!) + m.forEach((v1, v2, m) => { + expectType(v1) + expectType(v2) + expectType>(m) + }) + s.forEach((v1, v2, s) => { + expectType(v1) + expectType(v2) + expectType>(s) + }) + }) + + it('composite types as values', () => { + const m = reactive(new Map()) + const s = reactive(new Set<{a: number}>()) + m.set('a', {a: 1}) + s.add({a: 1}) + + expectType<{a: number}>(m.get('a')!) + m.forEach((v1, v2, m) => { + expectType<{a: number}>(v1) + expectType(v2) + }) + s.forEach((v1, v2, s) => { + expectType<{a: number}>(v1) + expectType<{a: number}>(v2) + }) + }) + + it('composite type as key', () => { + const m = reactive(new Map<{a: number}, string>()) + m.set({a: 1}, 'a') + m.forEach((v1, v2, m) => { + expectType(v1) + expectType<{a: number}>(v2) + }) + }) + + it('ref as value', () => { + const m = reactive(new Map>()) + const s = reactive(new Set>()) + m.set('a', ref(1)) + s.add(ref('b')) + + expectType>>(m) + expectType>>(s) + expectType>(m.get('a')!) + + m.forEach((v1, v2, m) => { + expectType>(v1) + expectType(v2) + expectType>>(m) + }) + + s.forEach((v1, v2, s) => { + expectType>(v1) + expectType>(v2) + expectType>>(s) + }) + }) + + it('when value is an object and ref is used as an object property', () => { + const m = reactive(new Map }>()) + const s = reactive(new Set<{ foo:Ref }>()) + m.set('a', { + foo: ref(1) + }) + s.add({ + foo: ref(1) + }) + + + expectType }>>(m) + expectType }>>(s) + expectType<{foo: number}>(m.get('a')!) + + m.forEach((v1, v2, m) => { + expectType<{ foo: number }>(v1) + expectType(v2) + expectType }>>(m) + }) + + s.forEach((v1, v2, s) => { + expectType<{foo: number}>(v1) + expectType<{foo: number}>(v2) + expectType}>>(s) + }) + }) + it('When the key is an object and the responsive data is an object property', () => { + const m = reactive(new Map<{ foo: Ref }, string>()) + const obj = { + foo: ref(1) + } + m.set(obj, 'a') + + expectType }, string>>(m) + m.forEach((v1, v2, m) => { + expectType(v1) + expectType<{foo: number}>(v2) + expectType }, string>>(m) + }) + + }) +}) From da113ee5c751fa9a8058ee7addb0e807124a1775 Mon Sep 17 00:00:00 2001 From: hubvue Date: Tue, 26 Jul 2022 10:17:52 +0800 Subject: [PATCH 2/4] test(types): collection type incompatibility --- .../reactivity/__tests__/collections/shallowReadonly.spec.ts | 4 ++-- packages/reactivity/__tests__/shallowReadonly.spec.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/__tests__/collections/shallowReadonly.spec.ts b/packages/reactivity/__tests__/collections/shallowReadonly.spec.ts index 11eb002fc88..f1923840f90 100644 --- a/packages/reactivity/__tests__/collections/shallowReadonly.spec.ts +++ b/packages/reactivity/__tests__/collections/shallowReadonly.spec.ts @@ -2,7 +2,7 @@ import { isReactive, isReadonly, shallowReadonly } from '../../src' describe('reactivity/collections', () => { describe('shallowReadonly/Map', () => { - ;[Map, WeakMap].forEach(Collection => { + ;[Map, WeakMap].forEach((Collection: any) => { test('should make the map/weak-map readonly', () => { const key = {} const val = { foo: 1 } @@ -81,7 +81,7 @@ describe('reactivity/collections', () => { describe('shallowReadonly/Set', () => { test('should make the set/weak-set readonly', () => { - ;[Set, WeakSet].forEach(Collection => { + ;[Set, WeakSet].forEach((Collection: any) => { const obj = { foo: 1 } const original = new Collection([obj]) const sroSet = shallowReadonly(original) diff --git a/packages/reactivity/__tests__/shallowReadonly.spec.ts b/packages/reactivity/__tests__/shallowReadonly.spec.ts index 79d4376cc01..853eec7357f 100644 --- a/packages/reactivity/__tests__/shallowReadonly.spec.ts +++ b/packages/reactivity/__tests__/shallowReadonly.spec.ts @@ -38,7 +38,7 @@ describe('reactivity/shallowReadonly', () => { }) describe('collection/Map', () => { - ;[Map, WeakMap].forEach(Collection => { + ;[Map, WeakMap].forEach((Collection: any) => { test('should make the map/weak-map readonly', () => { const key = {} const val = { foo: 1 } @@ -117,7 +117,7 @@ describe('reactivity/shallowReadonly', () => { describe('collection/Set', () => { test('should make the set/weak-set readonly', () => { - ;[Set, WeakSet].forEach(Collection => { + ;[Set, WeakSet].forEach((Collection: any) => { const obj = { foo: 1 } const original = new Collection([obj]) const sroSet = shallowReadonly(original) From 98c44bc20529ba19bffc97d285a6df1a8e7f6618 Mon Sep 17 00:00:00 2001 From: hubvue Date: Tue, 26 Jul 2022 10:28:18 +0800 Subject: [PATCH 3/4] test(types): add non-reactive collection types test --- test-dts/reactivity.test-d.ts | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test-dts/reactivity.test-d.ts b/test-dts/reactivity.test-d.ts index 9aaa574bb3c..1096801dba9 100644 --- a/test-dts/reactivity.test-d.ts +++ b/test-dts/reactivity.test-d.ts @@ -172,6 +172,50 @@ describe('collection-type', () => { expectType<{foo: number}>(v2) expectType }, string>>(m) }) + }) + it('normal use under non-reactive', () => { + const m = new Map() + const s = new Set() + const mr = new Map<{a: Ref}, {b: Ref}>() + const sr = new Set<{a: Ref}>() + const objA = { + a: ref(1) + } + const objB = { + b: ref(1) + } + + m.set('a', 1) + s.add('b') + mr.set(objA, objB) + sr.add(objA) + expectType>(m) + expectType>(s) + expectType}, {b: Ref}>>(mr) + expectType}>>(sr) + expectType(m.get('a')!) + expectType<{b: Ref}>(mr.get(objA)!) + + m.forEach((v1, v2, m) => { + expectType(v1) + expectType(v2) + expectType>(m) + }) + s.forEach((v1, v2, s) => { + expectType(v1) + expectType(v2) + expectType>(s) + }) + mr.forEach((v1, v2, m) => { + expectType<{b: Ref}>(v1) + expectType<{a: Ref}>(v2) + expectType}, {b: Ref}>>(m) + }) + sr.forEach((v1, v2, s) => { + expectType<{a: Ref}>(v1) + expectType<{a: Ref}>(v2) + expectType}>>(s) + }) }) }) From e3287a1b87572f81876928e36ca85d4e7187af3b Mon Sep 17 00:00:00 2001 From: hubvue Date: Wed, 27 Jul 2022 16:04:13 +0800 Subject: [PATCH 4/4] fix: export declare global Map --- packages/reactivity/index.d.ts | 20 +++++++++++++++++++ packages/reactivity/package.json | 3 ++- packages/reactivity/src/collectionHandlers.ts | 2 +- packages/reactivity/src/index.ts | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 packages/reactivity/index.d.ts diff --git a/packages/reactivity/index.d.ts b/packages/reactivity/index.d.ts new file mode 100644 index 00000000000..5421ac1b624 --- /dev/null +++ b/packages/reactivity/index.d.ts @@ -0,0 +1,20 @@ +import { CollectionUnwrapRefs } from './dist/reactivity' + +export * from './dist/reactivity' + +declare global { + interface Map { + get>>(this: T, key: K): CollectionUnwrapRefs | undefined + forEach>>(this: T, callbackfn: (value: CollectionUnwrapRefs, key: CollectionUnwrapRefs, map: T) => void, thisArg?: any): void; + } + interface WeakMap { + get>>(this: T, key: K): CollectionUnwrapRefs | undefined + forEach>>(this: T, callbackfn: (value: CollectionUnwrapRefs, key: CollectionUnwrapRefs, map: T) => void, thisArg?: any): void; + } + interface Set { + forEach>>(this: TT, callbackfn: (value1: CollectionUnwrapRefs, value2: CollectionUnwrapRefs, set: TT) => void, thisArg?: any): void; + } + interface WeakSet { + forEach>>(this: TT, callbackfn: (value1: CollectionUnwrapRefs, value2: CollectionUnwrapRefs, set: TT) => void, thisArg?: any): void; + } +} diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index 5ddf1168227..c375f2188e8 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -4,11 +4,12 @@ "description": "@vue/reactivity", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", - "types": "dist/reactivity.d.ts", + "types": "index.d.ts", "unpkg": "dist/reactivity.global.js", "jsdelivr": "dist/reactivity.global.js", "files": [ "index.js", + "index.d.ts", "dist" ], "sideEffects": false, diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index d5e1975c73d..360c348dc26 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -12,7 +12,7 @@ import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect' import { TrackOpTypes, TriggerOpTypes } from './operations' import { capitalize, hasOwn, hasChanged, toRawType, isMap } from '@vue/shared' -type CollectionUnwrapRefs = +export type CollectionUnwrapRefs = IsCollectionReactive extends true ? ReactiveObject : IsCollectionReadonly extends true diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index a7a03b8c573..306fb519d70 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -66,3 +66,4 @@ export { onScopeDispose } from './effectScope' export { TrackOpTypes, TriggerOpTypes } from './operations' +export { CollectionUnwrapRefs } from './collectionHandlers'