| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- import { isArray } from '@vue/shared'
- import { TrackOpTypes } from './constants'
- import { ARRAY_ITERATE_KEY, track } from './dep'
- import {
- isProxy,
- isReactive,
- isReadonly,
- isShallow,
- toRaw,
- toReactive,
- toReadonly,
- } from './reactive'
- import { endBatch, setActiveSub, startBatch } from './system'
- /**
- * Track array iteration and return:
- * - if input is reactive: a cloned raw array with reactive values
- * - if input is non-reactive or shallowReactive: the original raw array
- */
- export function reactiveReadArray<T>(array: T[]): T[] {
- const raw = toRaw(array)
- if (raw === array) return raw
- track(raw, TrackOpTypes.ITERATE, ARRAY_ITERATE_KEY)
- return isShallow(array) ? raw : raw.map(toReactive)
- }
- /**
- * Track array iteration and return raw array
- */
- export function shallowReadArray<T>(arr: T[]): T[] {
- track((arr = toRaw(arr)), TrackOpTypes.ITERATE, ARRAY_ITERATE_KEY)
- return arr
- }
- function toWrapped(target: unknown, item: unknown) {
- if (isReadonly(target)) {
- return isReactive(target) ? toReadonly(toReactive(item)) : toReadonly(item)
- }
- return toReactive(item)
- }
- export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
- __proto__: null,
- [Symbol.iterator]() {
- return iterator(this, Symbol.iterator, item => toWrapped(this, item))
- },
- concat(...args: unknown[]) {
- return reactiveReadArray(this).concat(
- ...args.map(x => (isArray(x) ? reactiveReadArray(x) : x)),
- )
- },
- entries() {
- return iterator(this, 'entries', (value: [number, unknown]) => {
- value[1] = toWrapped(this, value[1])
- return value
- })
- },
- every(
- fn: (item: unknown, index: number, array: unknown[]) => unknown,
- thisArg?: unknown,
- ) {
- return apply(this, 'every', fn, thisArg, undefined, arguments)
- },
- filter(
- fn: (item: unknown, index: number, array: unknown[]) => unknown,
- thisArg?: unknown,
- ) {
- return apply(
- this,
- 'filter',
- fn,
- thisArg,
- v => v.map((item: unknown) => toWrapped(this, item)),
- arguments,
- )
- },
- find(
- fn: (item: unknown, index: number, array: unknown[]) => boolean,
- thisArg?: unknown,
- ) {
- return apply(
- this,
- 'find',
- fn,
- thisArg,
- item => toWrapped(this, item),
- arguments,
- )
- },
- findIndex(
- fn: (item: unknown, index: number, array: unknown[]) => boolean,
- thisArg?: unknown,
- ) {
- return apply(this, 'findIndex', fn, thisArg, undefined, arguments)
- },
- findLast(
- fn: (item: unknown, index: number, array: unknown[]) => boolean,
- thisArg?: unknown,
- ) {
- return apply(
- this,
- 'findLast',
- fn,
- thisArg,
- item => toWrapped(this, item),
- arguments,
- )
- },
- findLastIndex(
- fn: (item: unknown, index: number, array: unknown[]) => boolean,
- thisArg?: unknown,
- ) {
- return apply(this, 'findLastIndex', fn, thisArg, undefined, arguments)
- },
- // flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implement
- forEach(
- fn: (item: unknown, index: number, array: unknown[]) => unknown,
- thisArg?: unknown,
- ) {
- return apply(this, 'forEach', fn, thisArg, undefined, arguments)
- },
- includes(...args: unknown[]) {
- return searchProxy(this, 'includes', args)
- },
- indexOf(...args: unknown[]) {
- return searchProxy(this, 'indexOf', args)
- },
- join(separator?: string) {
- return reactiveReadArray(this).join(separator)
- },
- // keys() iterator only reads `length`, no optimization required
- lastIndexOf(...args: unknown[]) {
- return searchProxy(this, 'lastIndexOf', args)
- },
- map(
- fn: (item: unknown, index: number, array: unknown[]) => unknown,
- thisArg?: unknown,
- ) {
- return apply(this, 'map', fn, thisArg, undefined, arguments)
- },
- pop() {
- return noTracking(this, 'pop')
- },
- push(...args: unknown[]) {
- return noTracking(this, 'push', args)
- },
- reduce(
- fn: (
- acc: unknown,
- item: unknown,
- index: number,
- array: unknown[],
- ) => unknown,
- ...args: unknown[]
- ) {
- return reduce(this, 'reduce', fn, args)
- },
- reduceRight(
- fn: (
- acc: unknown,
- item: unknown,
- index: number,
- array: unknown[],
- ) => unknown,
- ...args: unknown[]
- ) {
- return reduce(this, 'reduceRight', fn, args)
- },
- shift() {
- return noTracking(this, 'shift')
- },
- // slice could use ARRAY_ITERATE but also seems to beg for range tracking
- some(
- fn: (item: unknown, index: number, array: unknown[]) => unknown,
- thisArg?: unknown,
- ) {
- return apply(this, 'some', fn, thisArg, undefined, arguments)
- },
- splice(...args: unknown[]) {
- return noTracking(this, 'splice', args)
- },
- toReversed() {
- // @ts-expect-error user code may run in es2016+
- return reactiveReadArray(this).toReversed()
- },
- toSorted(comparer?: (a: unknown, b: unknown) => number) {
- // @ts-expect-error user code may run in es2016+
- return reactiveReadArray(this).toSorted(comparer)
- },
- toSpliced(...args: unknown[]) {
- // @ts-expect-error user code may run in es2016+
- return (reactiveReadArray(this).toSpliced as any)(...args)
- },
- unshift(...args: unknown[]) {
- return noTracking(this, 'unshift', args)
- },
- values() {
- return iterator(this, 'values', item => toWrapped(this, item))
- },
- }
- // instrument iterators to take ARRAY_ITERATE dependency
- function iterator(
- self: unknown[],
- method: keyof Array<unknown>,
- wrapValue: (value: any) => unknown,
- ) {
- // note that taking ARRAY_ITERATE dependency here is not strictly equivalent
- // to calling iterate on the proxied array.
- // creating the iterator does not access any array property:
- // it is only when .next() is called that length and indexes are accessed.
- // pushed to the extreme, an iterator could be created in one effect scope,
- // partially iterated in another, then iterated more in yet another.
- // given that JS iterator can only be read once, this doesn't seem like
- // a plausible use-case, so this tracking simplification seems ok.
- const arr = shallowReadArray(self)
- const iter = (arr[method] as any)() as IterableIterator<unknown> & {
- _next: IterableIterator<unknown>['next']
- }
- if (arr !== self && !isShallow(self)) {
- iter._next = iter.next
- iter.next = () => {
- const result = iter._next()
- if (!result.done) {
- result.value = wrapValue(result.value)
- }
- return result
- }
- }
- return iter
- }
- // in the codebase we enforce es2016, but user code may run in environments
- // higher than that
- type ArrayMethods = keyof Array<any> | 'findLast' | 'findLastIndex'
- const arrayProto = Array.prototype
- // instrument functions that read (potentially) all items
- // to take ARRAY_ITERATE dependency
- function apply(
- self: unknown[],
- method: ArrayMethods,
- fn: (item: unknown, index: number, array: unknown[]) => unknown,
- thisArg?: unknown,
- wrappedRetFn?: (result: any) => unknown,
- args?: IArguments,
- ) {
- const arr = shallowReadArray(self)
- const needsWrap = arr !== self && !isShallow(self)
- // @ts-expect-error our code is limited to es2016 but user code is not
- const methodFn = arr[method]
- // #11759
- // If the method being called is from a user-extended Array, the arguments will be unknown
- // (unknown order and unknown parameter types). In this case, we skip the shallowReadArray
- // handling and directly call apply with self.
- if (methodFn !== arrayProto[method as any]) {
- const result = methodFn.apply(self, args)
- return needsWrap ? toReactive(result) : result
- }
- let wrappedFn = fn
- if (arr !== self) {
- if (needsWrap) {
- wrappedFn = function (this: unknown, item, index) {
- return fn.call(this, toWrapped(self, item), index, self)
- }
- } else if (fn.length > 2) {
- wrappedFn = function (this: unknown, item, index) {
- return fn.call(this, item, index, self)
- }
- }
- }
- const result = methodFn.call(arr, wrappedFn, thisArg)
- return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result
- }
- // instrument reduce and reduceRight to take ARRAY_ITERATE dependency
- function reduce(
- self: unknown[],
- method: keyof Array<any>,
- fn: (acc: unknown, item: unknown, index: number, array: unknown[]) => unknown,
- args: unknown[],
- ) {
- const arr = shallowReadArray(self)
- let wrappedFn = fn
- if (arr !== self) {
- if (!isShallow(self)) {
- wrappedFn = function (this: unknown, acc, item, index) {
- return fn.call(this, acc, toWrapped(self, item), index, self)
- }
- } else if (fn.length > 3) {
- wrappedFn = function (this: unknown, acc, item, index) {
- return fn.call(this, acc, item, index, self)
- }
- }
- }
- return (arr[method] as any)(wrappedFn, ...args)
- }
- // instrument identity-sensitive methods to account for reactive proxies
- function searchProxy(
- self: unknown[],
- method: keyof Array<any>,
- args: unknown[],
- ) {
- const arr = toRaw(self) as any
- track(arr, TrackOpTypes.ITERATE, ARRAY_ITERATE_KEY)
- // we run the method using the original args first (which may be reactive)
- const res = arr[method](...args)
- // if that didn't work, run it again using raw values.
- if ((res === -1 || res === false) && isProxy(args[0])) {
- args[0] = toRaw(args[0])
- return arr[method](...args)
- }
- return res
- }
- // instrument length-altering mutation methods to avoid length being tracked
- // which leads to infinite loops in some cases (#2137)
- function noTracking(
- self: unknown[],
- method: keyof Array<any>,
- args: unknown[] = [],
- ) {
- startBatch()
- const prevSub = setActiveSub()
- const res = (toRaw(self) as any)[method].apply(self, args)
- setActiveSub(prevSub)
- endBatch()
- return res
- }
|