fix(reactivity): ensure extended method arguments are not lost (#11574)

close #11570
This commit is contained in:
edison 2024-08-10 15:54:38 +08:00 committed by GitHub
parent 63b7c01d0f
commit 4085def1ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 113 additions and 11 deletions

View File

@ -622,5 +622,100 @@ describe('reactivity/reactive/Array', () => {
const firstItem = Array.from(deep.values())[0] const firstItem = Array.from(deep.values())[0]
expect(isReactive(firstItem)).toBe(true) expect(isReactive(firstItem)).toBe(true)
}) })
test('extend methods', () => {
class Collection extends Array {
// @ts-expect-error
every(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
return super.every(obj => obj.id === foo)
}
// @ts-expect-error
filter(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
return super.filter(obj => obj.id === foo)
}
// @ts-expect-error
find(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
return super.find(obj => obj.id === foo)
}
// @ts-expect-error
findIndex(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
return super.findIndex(obj => obj.id === bar)
}
findLast(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
// @ts-expect-error our code is limited to es2016 but user code is not
return super.findLast(obj => obj.id === bar)
}
findLastIndex(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
return super.findIndex(obj => obj.id === bar)
}
// @ts-expect-error
forEach(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
}
// @ts-expect-error
map(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
return super.map(obj => obj.value)
}
// @ts-expect-error
some(foo: any, bar: any, baz: any) {
expect(foo).toBe('foo')
expect(bar).toBe('bar')
expect(baz).toBe('baz')
return super.some(obj => obj.id === baz)
}
}
const state = reactive({
things: new Collection(),
})
const foo = { id: 'foo', value: '1' }
const bar = { id: 'bar', value: '2' }
const baz = { id: 'baz', value: '3' }
state.things.push(foo)
state.things.push(bar)
state.things.push(baz)
expect(state.things.every('foo', 'bar', 'baz')).toBe(false)
expect(state.things.filter('foo', 'bar', 'baz')).toEqual([foo])
expect(state.things.find('foo', 'bar', 'baz')).toBe(foo)
expect(state.things.findIndex('foo', 'bar', 'baz')).toBe(1)
expect(state.things.findLast('foo', 'bar', 'baz')).toBe(bar)
expect(state.things.findLastIndex('foo', 'bar', 'baz')).toBe(1)
expect(state.things.forEach('foo', 'bar', 'baz')).toBeUndefined()
expect(state.things.map('foo', 'bar', 'baz')).toEqual(['1', '2', '3'])
expect(state.things.some('foo', 'bar', 'baz')).toBe(true)
})
}) })
}) })

View File

@ -47,42 +47,42 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
fn: (item: unknown, index: number, array: unknown[]) => unknown, fn: (item: unknown, index: number, array: unknown[]) => unknown,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'every', fn, thisArg) return apply(this, 'every', fn, thisArg, undefined, arguments)
}, },
filter( filter(
fn: (item: unknown, index: number, array: unknown[]) => unknown, fn: (item: unknown, index: number, array: unknown[]) => unknown,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'filter', fn, thisArg, v => v.map(toReactive)) return apply(this, 'filter', fn, thisArg, v => v.map(toReactive), arguments)
}, },
find( find(
fn: (item: unknown, index: number, array: unknown[]) => boolean, fn: (item: unknown, index: number, array: unknown[]) => boolean,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'find', fn, thisArg, toReactive) return apply(this, 'find', fn, thisArg, toReactive, arguments)
}, },
findIndex( findIndex(
fn: (item: unknown, index: number, array: unknown[]) => boolean, fn: (item: unknown, index: number, array: unknown[]) => boolean,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'findIndex', fn, thisArg) return apply(this, 'findIndex', fn, thisArg, undefined, arguments)
}, },
findLast( findLast(
fn: (item: unknown, index: number, array: unknown[]) => boolean, fn: (item: unknown, index: number, array: unknown[]) => boolean,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'findLast', fn, thisArg, toReactive) return apply(this, 'findLast', fn, thisArg, toReactive, arguments)
}, },
findLastIndex( findLastIndex(
fn: (item: unknown, index: number, array: unknown[]) => boolean, fn: (item: unknown, index: number, array: unknown[]) => boolean,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'findLastIndex', fn, thisArg) return apply(this, 'findLastIndex', fn, thisArg, undefined, arguments)
}, },
// flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implement // flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implement
@ -91,7 +91,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
fn: (item: unknown, index: number, array: unknown[]) => unknown, fn: (item: unknown, index: number, array: unknown[]) => unknown,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'forEach', fn, thisArg) return apply(this, 'forEach', fn, thisArg, undefined, arguments)
}, },
includes(...args: unknown[]) { includes(...args: unknown[]) {
@ -116,7 +116,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
fn: (item: unknown, index: number, array: unknown[]) => unknown, fn: (item: unknown, index: number, array: unknown[]) => unknown,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'map', fn, thisArg) return apply(this, 'map', fn, thisArg, undefined, arguments)
}, },
pop() { pop() {
@ -161,7 +161,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
fn: (item: unknown, index: number, array: unknown[]) => unknown, fn: (item: unknown, index: number, array: unknown[]) => unknown,
thisArg?: unknown, thisArg?: unknown,
) { ) {
return apply(this, 'some', fn, thisArg) return apply(this, 'some', fn, thisArg, undefined, arguments)
}, },
splice(...args: unknown[]) { splice(...args: unknown[]) {
@ -227,6 +227,7 @@ function iterator(
// higher than that // higher than that
type ArrayMethods = keyof Array<any> | 'findLast' | 'findLastIndex' type ArrayMethods = keyof Array<any> | 'findLast' | 'findLastIndex'
const arrayProto = Array.prototype
// instrument functions that read (potentially) all items // instrument functions that read (potentially) all items
// to take ARRAY_ITERATE dependency // to take ARRAY_ITERATE dependency
function apply( function apply(
@ -235,8 +236,15 @@ function apply(
fn: (item: unknown, index: number, array: unknown[]) => unknown, fn: (item: unknown, index: number, array: unknown[]) => unknown,
thisArg?: unknown, thisArg?: unknown,
wrappedRetFn?: (result: any) => unknown, wrappedRetFn?: (result: any) => unknown,
args?: IArguments,
) { ) {
const arr = shallowReadArray(self) const arr = shallowReadArray(self)
let methodFn
// @ts-expect-error our code is limited to es2016 but user code is not
if ((methodFn = arr[method]) !== arrayProto[method]) {
return methodFn.apply(arr, args)
}
let needsWrap = false let needsWrap = false
let wrappedFn = fn let wrappedFn = fn
if (arr !== self) { if (arr !== self) {
@ -251,8 +259,7 @@ function apply(
} }
} }
} }
// @ts-expect-error our code is limited to es2016 but user code is not const result = methodFn.call(arr, wrappedFn, thisArg)
const result = arr[method](wrappedFn, thisArg)
return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result
} }