Skip to content

Commit 8ea930d

Browse files
committed
perf(core): lots of performance improvements
1 parent d4ac471 commit 8ea930d

File tree

4 files changed

+72
-108
lines changed

4 files changed

+72
-108
lines changed

packages/core/src/bind/connectObservable.test.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Subject,
1515
throwError,
1616
Observable,
17+
merge,
1718
} from "rxjs"
1819
import {
1920
delay,
@@ -385,11 +386,17 @@ describe("connectObservable", () => {
385386
})
386387

387388
it("allows to retry the errored observable after a grace period of time", async () => {
388-
let errStream = new Subject<string>()
389+
const errStream = new Subject<string>()
390+
const nextStream = new Subject<string>()
389391
const [useError, error$] = bind(
390-
defer(() => {
391-
return (errStream = new Subject<string>())
392-
}),
392+
merge(
393+
errStream.pipe(
394+
map((x) => {
395+
throw x
396+
}),
397+
),
398+
nextStream,
399+
),
393400
)
394401

395402
const ErrorComponent = () => {
@@ -398,7 +405,7 @@ describe("connectObservable", () => {
398405
}
399406

400407
const errorCallback = jest.fn()
401-
error$.pipe(catchError(() => [])).subscribe()
408+
error$.pipe(catchError((_, caught) => caught)).subscribe()
402409
const { unmount } = render(
403410
<TestErrorBoundary onError={errorCallback}>
404411
<Suspense fallback={<div>Loading...</div>}>
@@ -411,7 +418,7 @@ describe("connectObservable", () => {
411418
expect(screen.queryByText("ALL GOOD")).toBeNull()
412419

413420
await componentAct(async () => {
414-
errStream.error("controlled error")
421+
errStream.next("controlled error")
415422
await wait(50)
416423
})
417424

@@ -439,7 +446,7 @@ describe("connectObservable", () => {
439446
expect(screen.queryByText("Loading...")).not.toBeNull()
440447

441448
await componentAct(async () => {
442-
errStream.next("ALL GOOD")
449+
nextStream.next("ALL GOOD")
443450
await wait(50)
444451
})
445452

packages/core/src/internal/BehaviorObservable.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,3 @@ import { Observable } from "rxjs"
33
export interface BehaviorObservable<T> extends Observable<T> {
44
getValue: () => any
55
}
6-
7-
export const enum Action {
8-
Error,
9-
Value,
10-
Suspense,
11-
}

packages/core/src/internal/react-enhancer.ts

Lines changed: 38 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,59 @@
11
import { Observable } from "rxjs"
22
import { SUSPENSE } from "../SUSPENSE"
3-
import { BehaviorObservable, Action } from "./BehaviorObservable"
3+
import { BehaviorObservable } from "./BehaviorObservable"
44
import { EMPTY_VALUE } from "./empty-value"
55

66
const reactEnhancer = <T>(source$: Observable<T>): BehaviorObservable<T> => {
7-
const result = new Observable<T>((subscriber) => {
8-
let latestValue = EMPTY_VALUE
9-
return source$.subscribe(
10-
(value) => {
11-
if (!Object.is(latestValue, value)) {
12-
subscriber.next((latestValue = value))
13-
}
14-
},
15-
(e) => {
16-
subscriber.error(e)
17-
},
18-
)
19-
}) as BehaviorObservable<T>
7+
const result = new Observable<T>((subscriber) =>
8+
source$.subscribe(subscriber),
9+
) as BehaviorObservable<T>
2010

21-
let promise: undefined | { type: Action.Suspense; payload: Promise<T | void> }
22-
let error:
23-
| typeof EMPTY_VALUE
24-
| { type: Action.Error; payload: any } = EMPTY_VALUE
25-
const getValue = (): { type: Action; payload: any } => {
11+
let promise: Promise<T | void> | undefined
12+
let error: any = EMPTY_VALUE
13+
const getValue = (): T => {
2614
let timeoutToken
2715
if (error !== EMPTY_VALUE) {
2816
clearTimeout(timeoutToken)
2917
timeoutToken = setTimeout(() => {
3018
error = EMPTY_VALUE
3119
}, 50)
32-
return error
20+
throw error
3321
}
3422

3523
try {
36-
return {
37-
type: Action.Value,
38-
payload: (source$ as BehaviorObservable<T>).getValue(),
39-
}
24+
return (source$ as BehaviorObservable<T>).getValue()
4025
} catch (e) {
41-
if (promise) return promise
42-
43-
let value:
44-
| typeof EMPTY_VALUE
45-
| { type: Action.Value; payload: T } = EMPTY_VALUE
46-
47-
promise = {
48-
type: Action.Suspense,
49-
payload: new Promise<T>((res) => {
50-
const subscription = result.subscribe(
51-
(v) => {
52-
if (v !== (SUSPENSE as any)) {
53-
value = { type: Action.Value, payload: v }
54-
subscription && subscription.unsubscribe()
55-
res(v)
56-
}
57-
},
58-
(e) => {
59-
error = { type: Action.Error, payload: e }
60-
timeoutToken = setTimeout(() => {
61-
error = EMPTY_VALUE
62-
}, 50)
63-
res()
64-
},
65-
)
66-
if (value !== EMPTY_VALUE) {
67-
subscription.unsubscribe()
68-
}
69-
}).finally(() => {
70-
promise = undefined
71-
}),
72-
}
26+
if (promise) throw promise
27+
28+
let value: typeof EMPTY_VALUE | T = EMPTY_VALUE
29+
30+
promise = new Promise<T>((res) => {
31+
const subscription = result.subscribe(
32+
(v) => {
33+
if (v !== (SUSPENSE as any)) {
34+
value = v
35+
subscription && subscription.unsubscribe()
36+
res(v)
37+
}
38+
},
39+
(e) => {
40+
error = e
41+
timeoutToken = setTimeout(() => {
42+
error = EMPTY_VALUE
43+
}, 50)
44+
res()
45+
},
46+
)
47+
if (value !== EMPTY_VALUE) {
48+
subscription.unsubscribe()
49+
}
50+
}).finally(() => {
51+
promise = undefined
52+
})
7353

74-
if (value !== EMPTY_VALUE) {
75-
return value
76-
}
54+
if (value !== EMPTY_VALUE) return value
7755

78-
return error !== EMPTY_VALUE ? error : promise
56+
throw error !== EMPTY_VALUE ? error : promise
7957
}
8058
}
8159
result.getValue = getValue
Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,41 @@
1-
import { useEffect, useReducer } from "react"
2-
import { BehaviorObservable, Action } from "./BehaviorObservable"
1+
import { useEffect, useState } from "react"
2+
import { BehaviorObservable } from "./BehaviorObservable"
33
import { SUSPENSE } from "../SUSPENSE"
44
import { EMPTY_VALUE } from "./empty-value"
55

6-
const reducer = (
7-
current: { type: Action; payload: any },
8-
action: { type: Action; payload: any },
9-
) =>
10-
current.type === action.type && Object.is(current.payload, action.payload)
11-
? current
12-
: action
13-
14-
const init = (source$: BehaviorObservable<any>) => source$.getValue()
15-
166
export const useObservable = <O>(
177
source$: BehaviorObservable<O>,
188
): Exclude<O, typeof SUSPENSE> => {
19-
const [state, dispatch] = useReducer(reducer, source$, init)
9+
const [state, setState] = useState(source$.getValue)
2010

2111
useEffect(() => {
12+
let prevVal: O | typeof SUSPENSE = EMPTY_VALUE
13+
let err: any = EMPTY_VALUE
14+
2215
const onNext = (value: O | typeof SUSPENSE) => {
23-
if ((value as any) === SUSPENSE) {
24-
dispatch(source$.getValue())
25-
} else {
26-
dispatch({
27-
type: Action.Value,
28-
payload: value,
29-
})
16+
if (value === SUSPENSE) {
17+
setState(source$.getValue)
18+
} else if (!Object.is(value, prevVal)) {
19+
setState(value)
3020
}
21+
prevVal = value
3122
}
32-
const onError = (error: any) =>
33-
dispatch({
34-
type: Action.Error,
35-
payload: error,
23+
const onError = (error: any) => {
24+
err = error
25+
setState(() => {
26+
throw error
3627
})
28+
}
3729

38-
let val: O | typeof SUSPENSE = SUSPENSE
39-
let err: any = EMPTY_VALUE
40-
let subscription = source$.subscribe(
41-
(v) => (val = v),
42-
(e) => (err = e),
43-
)
44-
if (err !== EMPTY_VALUE) return onError(err)
45-
onNext(val)
30+
let subscription = source$.subscribe(onNext, onError)
31+
if (err !== EMPTY_VALUE) return
32+
if (prevVal === EMPTY_VALUE) onNext(SUSPENSE)
4633
const t = subscription
4734
subscription = source$.subscribe(onNext, onError)
4835
t.unsubscribe()
4936

5037
return () => subscription.unsubscribe()
5138
}, [source$])
5239

53-
const { type, payload } = state
54-
if (type === Action.Value) return payload
55-
throw payload
40+
return state
5641
}

0 commit comments

Comments
 (0)