Skip to content

Commit

Permalink
fix(reactivity): shallowReactive for collections (#1204)
Browse files Browse the repository at this point in the history
close #1202
  • Loading branch information
pikax committed May 18, 2020
1 parent ba62ccd commit 488e2bc
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 15 deletions.
27 changes: 27 additions & 0 deletions packages/reactivity/__tests__/reactive.spec.ts
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
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
Expand Up @@ -7,7 +7,8 @@ import {
} from './baseHandlers'
import {
mutableCollectionHandlers,
readonlyCollectionHandlers
readonlyCollectionHandlers,
shallowCollectionHandlers
} from './collectionHandlers'
import { UnwrapRef, Ref } from './ref'

Expand Down Expand Up @@ -67,7 +68,7 @@ export function shallowReactive<T extends object>(target: T): T {
target,
false,
shallowReactiveHandlers,
mutableCollectionHandlers
shallowCollectionHandlers
)
}

Expand Down

0 comments on commit 488e2bc

Please sign in to comment.