Skip to content

Commit

Permalink
unify spy() and observe API
Browse files Browse the repository at this point in the history
  • Loading branch information
phiresky committed Jul 15, 2020
1 parent e0e4dcb commit c62c758
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 151 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@
- Update readme to reflect correct scripts by [@elektronik2k5](https://github.com/elektronik2k5)
- `prettier` related tweaks by [@elektronik2k5](https://github.com/elektronik2k5)
- Add full types for the spy() function by [@phiresky](https://github.com/phiresky)
- Unify the interfaces of spy() and observe() (BREAKING: the `.name` prop of the spy event is now the name of the property key (as in observe()), the name of the object is in `.objectName`)

# 5.15.4 / 4.15.4

Expand Down
4 changes: 2 additions & 2 deletions docs/refguide/spy.md
Expand Up @@ -43,8 +43,8 @@ Spy listeners always receive one object, which usually has at least a `type` fie
| delete | map | name, object (observable map instance), key, oldValue | yes |
| add | set | name, object (observable set instance), newValue | yes |
| delete | set | name, object (observable set instance), oldValue | yes |
| create | box | name, object (Observable box instance), newValue | yes |
| update | box | name, object (Observable box instance), newValue, oldValue | yes |
| create | box | object (Observable box instance), newValue | yes |
| update | box | object (Observable box instance), newValue, oldValue | yes |
| report-end | | spyReportEnd=true, time? (total execution time in ms) | no |

The `report-end` events are part of an earlier fired event that had `spyReportStart: true`.
Expand Down
5 changes: 2 additions & 3 deletions src/api/observe.ts
@@ -1,6 +1,5 @@
import {
IArrayChange,
IArraySplice,
IArrayDidChange,
IComputedValue,
IMapDidChange,
IObjectDidChange,
Expand All @@ -22,7 +21,7 @@ export function observe<T>(
): Lambda
export function observe<T>(
observableArray: IObservableArray<T>,
listener: (change: IArrayChange<T> | IArraySplice<T>) => void,
listener: (change: IArrayDidChange<T>) => void,
fireImmediately?: boolean
): Lambda
export function observe<V>(
Expand Down
35 changes: 24 additions & 11 deletions src/core/computedvalue.ts
Expand Up @@ -4,7 +4,6 @@ import {
IDerivationState_,
IEqualsComparer,
IObservable,
IValueDidChange,
Lambda,
TraceMode,
autorun,
Expand Down Expand Up @@ -36,7 +35,7 @@ import {
export interface IComputedValue<T> {
get(): T
set(value: T): void
observe_(listener: (change: IValueDidChange<T>) => void, fireImmediately?: boolean): Lambda
observe_(listener: (change: IComputedDidChange<T>) => void, fireImmediately?: boolean): Lambda
}

export interface IComputedValueOptions<T> {
Expand All @@ -49,7 +48,14 @@ export interface IComputedValueOptions<T> {
keepAlive?: boolean
}

const COMPUTE = "compute"
export type IComputedDidChange<T = any> = {
type: "update"
observableKind: "computed"
object: unknown
objectName: string
newValue: T
oldValue: T | undefined
}

/**
* A node in the state dependency root that observes other nodes, and can be observed itself.
Expand Down Expand Up @@ -178,18 +184,22 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
}

private trackAndCompute_(): boolean {
if (__DEV__ && isSpyEnabled()) {
spyReport({
object: this.scope_,
type: COMPUTE,
name: this.name_
})
}
const oldValue = this.value_
const wasSuspended =
/* see #1208 */ this.dependenciesState_ === IDerivationState_.NOT_TRACKING_
const newValue = this.computeValue_(true)

if (__DEV__ && isSpyEnabled()) {
spyReport({
observableKind: "computed",
objectName: this.name_,
object: this.scope_,
type: "update",
oldValue: this.value_,
newValue
} as IComputedDidChange)
}

const changed =
wasSuspended ||
isCaughtException(oldValue) ||
Expand Down Expand Up @@ -233,14 +243,17 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
}
}

observe_(listener: (change: IValueDidChange<T>) => void, fireImmediately?: boolean): Lambda {
observe_(listener: (change: IComputedDidChange<T>) => void, fireImmediately?: boolean): Lambda {
let firstTime = true
let prevValue: T | undefined = undefined
return autorun(() => {
// TODO: why is this in a different place than the spyReport() function? in all other observables it's called in the same place
let newValue = this.get()
if (!firstTime || fireImmediately) {
const prevU = untrackedStart()
listener({
observableKind: "computed",
objectName: this.name_,
type: UPDATE,
object: this,
newValue,
Expand Down
77 changes: 10 additions & 67 deletions src/core/spy.ts
@@ -1,81 +1,24 @@
import { IComputedDidChange } from "./computedvalue"
import { IValueDidChange } from "./../types/observablevalue"
import { IObjectDidChange } from "./../types/observableobject"
import { IArrayDidChange } from "./../types/observablearray"
import { Lambda, globalState, once, ISetDidChange, IMapDidChange } from "../internal"

export function isSpyEnabled() {
return __DEV__ && !!globalState.spyListeners.length
}

export type ObjectSpyEvent = {
observableKind: "object"
object: unknown
name: string
key: string | number | symbol
} & (
| {
type: "add"
newValue: unknown
}
| {
type: "update"
newValue: unknown
oldValue: unknown
}
| {
type: "remove"
oldValue: unknown
}
)

export type ArraySpyEvent = {
observableKind: "array"
name: string
index: number
object: unknown[]
} & (
| {
type: "update"
index: number
newValue: unknown
oldValue: unknown
}
| {
type: "splice"
removed: unknown[]
added: unknown[]
removedCount: number
addedCount: number
}
)

export type BoxSpyEvent =
| {
type: "create"
observableKind: "box"
name: string
newValue: unknown
}
| {
type: "update"
observableKind: "box"
name: string
newValue: unknown
oldValue: unknown
}

export type PureSpyEvent =
| { type: "action"; name: string; object: unknown; arguments: unknown[] }
| { type: "scheduled-reaction"; name: string }
| { type: "reaction"; name: string }
| { type: "compute"; object: unknown; name: string }
| { type: "error"; name: string; message: string; error: string }
| ObjectSpyEvent
| ArraySpyEvent
| (Omit<IMapDidChange<unknown, unknown>, "name"> & {
observableKind: "map"
name: string
key: unknown
})
| (Omit<ISetDidChange<unknown>, "name"> & { observableKind: "set"; name: string })
| BoxSpyEvent
| IComputedDidChange<unknown>
| IObjectDidChange<unknown>
| IArrayDidChange<unknown>
| IMapDidChange<unknown, unknown>
| ISetDidChange<unknown>
| IValueDidChange<unknown>
| { type: "report-end"; spyReportEnd: true; time?: number }

type SpyEvent = PureSpyEvent & { spyReportStart?: true }
Expand Down
3 changes: 1 addition & 2 deletions src/mobx.ts
Expand Up @@ -53,8 +53,7 @@ export {
IObservableArray,
IArrayWillChange,
IArrayWillSplice,
IArrayChange,
IArraySplice,
IArrayDidChange,
isObservableArray,
IKeyValueMap,
ObservableMap,
Expand Down
40 changes: 24 additions & 16 deletions src/types/observablearray.ts
Expand Up @@ -43,35 +43,40 @@ export interface IObservableArray<T = any> extends Array<T> {
toJSON(): T[]
}

export interface IArrayChange<T = any> {
type: "update"
interface IArrayBaseChange<T> {
object: IObservableArray<T>
observableKind: "array"
objectName: string
index: number
}

export type IArrayDidChange<T = any> = IArrayUpdate<T> | IArraySplice<T>

interface IArrayUpdate<T = any> extends IArrayBaseChange<T> {
type: "update"
newValue: T
oldValue: T
}

export interface IArraySplice<T = any> {
interface IArraySplice<T = any> extends IArrayBaseChange<T> {
type: "splice"
object: IObservableArray<T>
index: number
added: T[]
addedCount: number
removed: T[]
removedCount: number
}

export interface IArrayWillChange<T = any> {
type: "update"
object: IObservableArray<T>
index: number
type: "update"
newValue: T
}

export interface IArrayWillSplice<T = any> {
type: "splice"
object: IObservableArray<T>
index: number
type: "splice"
added: T[]
removedCount: number
}
Expand Down Expand Up @@ -115,7 +120,7 @@ export class ObservableArrayAdministration
changeListeners_
enhancer_: (newV: any, oldV: any | undefined) => any
dehancer: any
proxy_: any[] = undefined as any
proxy_!: IObservableArray<any>
lastKnownLength_ = 0

constructor(
Expand Down Expand Up @@ -144,12 +149,14 @@ export class ObservableArrayAdministration
}

observe_(
listener: (changeData: IArrayChange<any> | IArraySplice<any>) => void,
listener: (changeData: IArrayDidChange<any>) => void,
fireImmediately = false
): Lambda {
if (fireImmediately) {
listener(<IArraySplice<any>>{
observableKind: "array",
object: this.proxy_ as any,
objectName: this.atom_.name_,
type: "splice",
index: 0,
added: this.values_.slice(),
Expand Down Expand Up @@ -240,12 +247,13 @@ export class ObservableArrayAdministration
notifyArrayChildUpdate_(index: number, newValue: any, oldValue: any) {
const notifySpy = !this.owned_ && isSpyEnabled()
const notify = hasListeners(this)
const change =
const change: IArrayDidChange | null =
notify || notifySpy
? ({
observableKind: "array",
object: this.proxy_,
type: UPDATE,

objectName: this.atom_.name_,
index,
newValue,
oldValue
Expand All @@ -254,8 +262,7 @@ export class ObservableArrayAdministration

// The reason why this is on right hand side here (and not above), is this way the uglifier will drop it, but it won't
// cause any runtime overhead in development mode without NODE_ENV set, unless spying is enabled
if (__DEV__ && notifySpy)
spyReportStart({ ...change!, observableKind: "array", name: this.atom_.name_ })
if (__DEV__ && notifySpy) spyReportStart(change!)
this.atom_.reportChanged()
if (notify) notifyListeners(this, change)
if (__DEV__ && notifySpy) spyReportEnd()
Expand All @@ -264,10 +271,12 @@ export class ObservableArrayAdministration
notifyArraySplice_(index: number, added: any[], removed: any[]) {
const notifySpy = !this.owned_ && isSpyEnabled()
const notify = hasListeners(this)
const change =
const change: IArraySplice | null =
notify || notifySpy
? ({
observableKind: "array",
object: this.proxy_,
objectName: this.atom_.name_,
type: SPLICE,
index,
removed,
Expand All @@ -277,8 +286,7 @@ export class ObservableArrayAdministration
} as const)
: null

if (__DEV__ && notifySpy)
spyReportStart({ ...change!, observableKind: "array", name: this.atom_.name_ })
if (__DEV__ && notifySpy) spyReportStart(change!)
this.atom_.reportChanged()
// conform: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/observe
if (notify) notifyListeners(this, change)
Expand Down

0 comments on commit c62c758

Please sign in to comment.