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

perf(reactive): PoC for faster Array reactive handler #14

Open
wants to merge 4 commits into
base: computed-ondemand
Choose a base branch
from
Open
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
72 changes: 67 additions & 5 deletions packages/reactivity/src/baseHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import {
pauseTracking,
resetTracking,
pauseScheduling,
resetScheduling
resetScheduling,
ARRAY_ANY_VALUES_KEY,
ARRAY_ALL_VALUES_KEY
} from './effect'
import {
isObject,
Expand Down Expand Up @@ -54,14 +56,18 @@ function createArrayInstrumentations() {
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
const arr = toRaw(this) as any
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
track(arr, TrackOpTypes.GET, ARRAY_ANY_VALUES_KEY)
// we run the method using the original args first (which may be reactive)
const res = arr[key](...args)
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
return arr[key](...args.map(toRaw))
let hasRaw = false
const rawArgs = args.map(arg => {
const raw = toRaw(arg)
hasRaw ||= raw !== arg
return raw
})
return !hasRaw ? res : arr[key](...rawArgs)
} else {
return res
}
Expand Down Expand Up @@ -121,6 +127,54 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
const targetIsArray = isArray(target)

if (!isReadonly) {
if (targetIsArray) {
if (
key === 'reduce' ||
key === 'forEach' ||
key === 'filter' ||
key === 'map' ||
key === 'every' ||
// TODO: find(), findIndex(), some() no need to track full length
key === 'find' ||
key === 'findIndex' ||
key === 'some'
) {
return (...args: unknown[]) => {
track(target, TrackOpTypes.GET, ARRAY_ANY_VALUES_KEY)
return (target as any)[key].apply(target, args)
}
}
if (
key === 'shift' ||
key === 'unshift'
// key === 'splice'
) {
return (...args: unknown[]) => {
const oldLength = target.length
const res = (target as any)[key].apply(target, args)
if (oldLength !== target.length) {
pauseScheduling()
trigger(target, TriggerOpTypes.SET, ARRAY_ANY_VALUES_KEY)
trigger(target, TriggerOpTypes.SET, ARRAY_ALL_VALUES_KEY)
trigger(target, TriggerOpTypes.SET, 'length', target.length)
resetScheduling()
}
return res
}
}
if (key === 'sort' || key === 'reverse') {
return (...args: unknown[]) => {
const res = (target as any)[key].apply(target, args)
if (target.length >= 2) {
pauseScheduling()
trigger(target, TriggerOpTypes.SET, ARRAY_ANY_VALUES_KEY)
trigger(target, TriggerOpTypes.SET, ARRAY_ALL_VALUES_KEY)
resetScheduling()
}
return res
}
}
}
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
Expand All @@ -137,6 +191,9 @@ class BaseReactiveHandler implements ProxyHandler<Target> {

if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
if (targetIsArray && isIntegerKey(key)) {
track(target, TrackOpTypes.GET, ARRAY_ALL_VALUES_KEY)
}
}

if (shallow) {
Expand Down Expand Up @@ -194,11 +251,16 @@ class MutableReactiveHandler extends BaseReactiveHandler {
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
pauseScheduling()
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
if (isArray(target) && isIntegerKey(key)) {
trigger(target, TriggerOpTypes.SET, ARRAY_ANY_VALUES_KEY)
}
resetScheduling()
}
return result
}
Expand Down
4 changes: 4 additions & 0 deletions packages/reactivity/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export type DebuggerEventExtraInfo = {

export let activeEffect: ReactiveEffect | undefined

export const ARRAY_ANY_VALUES_KEY = Symbol(__DEV__ ? 'Array any values' : '')
export const ARRAY_ALL_VALUES_KEY = Symbol(__DEV__ ? 'Array all values' : '')
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')

Expand Down Expand Up @@ -341,6 +343,8 @@ export function trigger(
} else if (key === 'length' && isArray(target)) {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (key === ARRAY_ANY_VALUES_KEY) return
if (key === ARRAY_ALL_VALUES_KEY) return
if (key === 'length' || key >= newLength) {
deps.push(dep)
}
Expand Down
Loading