Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: shallowReactive for collections #1204

Merged
merged 1 commit into from
May 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/reactivity/__tests__/reactive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,32 @@ describe('reactivity/reactive', () => {
props.n = reactive({ foo: 2 })
expect(isReactive(props.n)).toBe(true)
})

test('should not observe when iterating', () => {
const shallowSet = shallowReactive(new Set())
const a = {}
shallowSet.add(a)

const spreadA = [...shallowSet][0]
expect(isReactive(spreadA)).toBe(false)
})

test('should not get reactive entry', () => {
const shallowMap = shallowReactive(new Map())
const a = {}
const key = 'a'

shallowMap.set(key, a)

expect(isReactive(shallowMap.get(key))).toBe(false)
})

test('should not get reactive on foreach', () => {
const shallowSet = shallowReactive(new Set())
const a = {}
shallowSet.add(a)

shallowSet.forEach(x => expect(isReactive(x)).toBe(false))
})
})
})
60 changes: 47 additions & 13 deletions packages/reactivity/src/collectionHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ const toReactive = <T extends unknown>(value: T): T =>
const toReadonly = <T extends unknown>(value: T): T =>
isObject(value) ? readonly(value) : value

const toShallow = <T extends unknown>(value: T): T => value

const getProto = <T extends CollectionTypes>(v: T): any =>
Reflect.getPrototypeOf(v)

function get(
target: MapTypes,
key: unknown,
wrap: typeof toReactive | typeof toReadonly
wrap: typeof toReactive | typeof toReadonly | typeof toShallow
) {
target = toRaw(target)
const rawKey = toRaw(key)
Expand Down Expand Up @@ -132,15 +134,15 @@ function clear(this: IterableCollections) {
return result
}

function createForEach(isReadonly: boolean) {
function createForEach(isReadonly: boolean, shallow: boolean) {
return function forEach(
this: IterableCollections,
callback: Function,
thisArg?: unknown
) {
const observed = this
const target = toRaw(observed)
const wrap = isReadonly ? toReadonly : toReactive
const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive
!isReadonly && track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
// important: create sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
Expand All @@ -152,14 +154,18 @@ function createForEach(isReadonly: boolean) {
}
}

function createIterableMethod(method: string | symbol, isReadonly: boolean) {
function createIterableMethod(
method: string | symbol,
isReadonly: boolean,
shallow: boolean
) {
return function(this: IterableCollections, ...args: unknown[]) {
const target = toRaw(this)
const isMap = target instanceof Map
const isPair = method === 'entries' || (method === Symbol.iterator && isMap)
const isKeyOnly = method === 'keys' && isMap
const innerIterator = getProto(target)[method].apply(target, args)
const wrap = isReadonly ? toReadonly : toReactive
const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive
!isReadonly &&
track(
target,
Expand Down Expand Up @@ -212,7 +218,22 @@ const mutableInstrumentations: Record<string, Function> = {
set,
delete: deleteEntry,
clear,
forEach: createForEach(false)
forEach: createForEach(false, false)
}

const shallowInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, toShallow)
},
get size() {
return size((this as unknown) as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, true)
}

const readonlyInstrumentations: Record<string, Function> = {
Expand All @@ -227,25 +248,34 @@ const readonlyInstrumentations: Record<string, Function> = {
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true)
forEach: createForEach(true, false)
}

const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
iteratorMethods.forEach(method => {
mutableInstrumentations[method as string] = createIterableMethod(
method,
false,
false
)
readonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
false
)
shallowInstrumentations[method as string] = createIterableMethod(
method,
true,
true
)
})

function createInstrumentationGetter(isReadonly: boolean) {
const instrumentations = isReadonly
? readonlyInstrumentations
: mutableInstrumentations
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = shallow
? shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations

return (
target: CollectionTypes,
Expand All @@ -271,11 +301,15 @@ function createInstrumentationGetter(isReadonly: boolean) {
}

export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false)
get: createInstrumentationGetter(false, false)
}

export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false, true)
}

export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(true)
get: createInstrumentationGetter(true, false)
}

function checkIdentityKeys(
Expand Down
5 changes: 3 additions & 2 deletions packages/reactivity/src/reactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
} from './baseHandlers'
import {
mutableCollectionHandlers,
readonlyCollectionHandlers
readonlyCollectionHandlers,
shallowCollectionHandlers
} from './collectionHandlers'
import { UnwrapRef, Ref } from './ref'
import { makeMap } from '@vue/shared'
Expand Down Expand Up @@ -68,7 +69,7 @@ export function shallowReactive<T extends object>(target: T): T {
target,
false,
shallowReactiveHandlers,
mutableCollectionHandlers
shallowCollectionHandlers
)
}

Expand Down