Skip to content

Commit 2f11fbf

Browse files
committed
Re-write solution to classes, because of perf issues with hooks
1 parent 48ba142 commit 2f11fbf

File tree

1 file changed

+71
-87
lines changed

1 file changed

+71
-87
lines changed

packages/focal/src/react/react.ts

Lines changed: 71 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -32,100 +32,88 @@ export namespace LiftWrapper {
3232
export type RenderCache = React.DOMElement<any, any> | null
3333
export type WrapperSubscription = Subscription | null
3434

35-
export interface SetRenderCache {
36-
(cache: RenderCache): void
37-
}
38-
39-
export interface SetSubscription {
40-
(sub: WrapperSubscription): void
41-
}
35+
export class Renderer<TProps> extends React.PureComponent<Props<TProps>> {
36+
subscription: WrapperSubscription = null
37+
renderCache: RenderCache = null
4238

43-
function useForceUpdate(): () => void {
44-
return React.useReducer(() => ({}), {})[1] as () => void
45-
}
39+
// variable to track sync emit from observable in Render{One, Many}.
40+
// prevent waste render during initial render & re-subscribe
41+
isSubscribed = false
4642

47-
const unsubscribe = (subscription: React.MutableRefObject<WrapperSubscription>) => {
48-
if (subscription.current) {
49-
subscription.current.unsubscribe()
43+
// eslint-disable-next-line camelcase
44+
UNSAFE_componentWillMount() {
45+
this.unsubscribe()
46+
this.subscribe(this.props)
5047
}
51-
}
5248

53-
const useSubscribe = <T>(
54-
newProps: Props<T>,
55-
subscription: React.MutableRefObject<WrapperSubscription>,
56-
renderCache: React.MutableRefObject<RenderCache>
57-
) => {
58-
const forceUpdate = useForceUpdate()
59-
const { props, component } = newProps
60-
61-
let n = 0
62-
// eslint-disable-next-line @typescript-eslint/no-use-before-define
63-
walkObservables(props, () => n += 1)
49+
// eslint-disable-next-line camelcase
50+
UNSAFE_componentWillReceiveProps(nextProps: Props<TProps>) {
51+
this.unsubscribe()
52+
this.subscribe(nextProps)
53+
}
6454

65-
// variable to track sync emit from observable in Render{One, Many}.
66-
// prevent waste render during initial render & re-subscribe
67-
let inSync = false
55+
componentWillUnmount() {
56+
this.setSubscription(null)
57+
}
6858

69-
const setCache: SetRenderCache = cache => {
70-
if (renderCache.current === cache) {
59+
setRenderCache(cache: RenderCache): void {
60+
if (this.renderCache === cache) {
7161
return
7262
}
7363

74-
renderCache.current = cache
64+
this.renderCache = cache
7565

76-
if (inSync) {
77-
forceUpdate()
66+
if (this.isSubscribed) {
67+
this.forceUpdate()
7868
}
7969
}
8070

81-
const setSubscription: SetSubscription = sub => subscription.current = sub
82-
83-
switch (n) {
84-
case 0:
85-
setSubscription(null)
86-
// eslint-disable-next-line @typescript-eslint/no-use-before-define
87-
setCache(render(component, props))
88-
break
89-
90-
// @NOTE original Calmm code below
91-
// The created object is never used and it looks like that
92-
// the useful work is done in the constructor.
93-
// Could this be replaced by a regular closure? Perhaps using
94-
// a class is an optimization?
95-
case 1:
96-
new RenderOne(renderCache, newProps, setCache, setSubscription) // eslint-disable-line
97-
break
98-
default:
99-
new RenderMany(renderCache, newProps, setCache, setSubscription, n) // eslint-disable-line
100-
break
71+
setSubscription(sub: WrapperSubscription): void {
72+
this.subscription = sub
10173
}
10274

103-
inSync = true
104-
}
75+
private unsubscribe() {
76+
if (this.subscription) {
77+
this.subscription.unsubscribe()
78+
}
79+
}
10580

106-
export const Renderer = <TProps>(props: Props<TProps>) => {
107-
const _subscription = React.useRef<WrapperSubscription>(null)
108-
const _renderCache = React.useRef<RenderCache>(null)
81+
private subscribe(newProps: Props<TProps>) {
82+
const { props, component } = newProps
10983

110-
// concurrent mode support
111-
// see https://codesandbox.io/s/x2p46v02z4?from-embed=&file=/src/BadCounter.jsx
112-
const subscription = { current: _subscription.current }
113-
const renderCache = { current: _renderCache.current }
84+
let n = 0
85+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
86+
walkObservables(props, () => n += 1)
11487

115-
React.useEffect(() => {
116-
_subscription.current = subscription.current
117-
_renderCache.current = renderCache.current
118-
})
88+
this.isSubscribed = false
11989

120-
React.useEffect(() => () => unsubscribe(subscription), [])
90+
switch (n) {
91+
case 0:
92+
this.setSubscription(null)
93+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
94+
this.setRenderCache(render(component, props))
95+
break
96+
97+
// @NOTE original Calmm code below
98+
// The created object is never used and it looks like that
99+
// the useful work is done in the constructor.
100+
// Could this be replaced by a regular closure? Perhaps using
101+
// a class is an optimization?
102+
case 1:
103+
new RenderOne(this, newProps) // eslint-disable-line
104+
break
105+
default:
106+
new RenderMany(this, newProps, n) // eslint-disable-line
107+
break
108+
}
121109

122-
unsubscribe(subscription)
123-
useSubscribe(props, subscription, renderCache)
110+
this.isSubscribed = true
111+
}
124112

125-
return renderCache.current || null
113+
render() {
114+
return this.renderCache || null
115+
}
126116
}
127-
128-
Renderer.displayName = 'LiftWrapper'
129117
}
130118

131119
// here we only say TProps, but a lifted component
@@ -346,10 +334,8 @@ class RenderOne<P> implements Subscription {
346334
private _receivedValue = false
347335

348336
constructor(
349-
private renderCache: React.MutableRefObject<LiftWrapper.RenderCache>,
350-
private newProps: LiftWrapper.Props<P>,
351-
private setRenderCache: LiftWrapper.SetRenderCache,
352-
private setSubscription: LiftWrapper.SetSubscription
337+
private renderer: LiftWrapper.Renderer<P>,
338+
private newProps: LiftWrapper.Props<P>
353339
) {
354340
walkObservables(
355341
newProps.props,
@@ -368,7 +354,7 @@ class RenderOne<P> implements Subscription {
368354
if (DEV_ENV && !this._receivedValue)
369355
warnEmptyObservable(getReactComponentName(newProps.component))
370356

371-
this.setSubscription(this)
357+
this.renderer.setSubscription(this)
372358
}
373359

374360
unsubscribe() {
@@ -383,14 +369,14 @@ class RenderOne<P> implements Subscription {
383369
const { component, props } = this.newProps
384370
const renderCache = render(component, props, [value])
385371

386-
if (!structEq(this.renderCache.current, renderCache)) {
387-
this.setRenderCache(renderCache)
372+
if (!structEq(this.renderer.renderCache, renderCache)) {
373+
this.renderer.setRenderCache(renderCache)
388374
}
389375
}
390376

391377
private _handleCompleted = () => {
392378
this._innerSubscription = null
393-
this.setSubscription(null)
379+
this.renderer.setSubscription(null)
394380
}
395381
}
396382

@@ -404,10 +390,8 @@ class RenderMany<P> implements Subscription {
404390
private _innerSubscriptions: (RxSubscription | null)[]
405391

406392
constructor(
407-
private renderCache: React.MutableRefObject<LiftWrapper.RenderCache>,
393+
private renderer: LiftWrapper.Renderer<P>,
408394
private newProps: LiftWrapper.Props<P>,
409-
private setRenderCache: LiftWrapper.SetRenderCache,
410-
private setSubscription: LiftWrapper.SetSubscription,
411395
N: number
412396
) {
413397
this._innerSubscriptions = []
@@ -447,7 +431,7 @@ class RenderMany<P> implements Subscription {
447431
break
448432
}
449433

450-
this.setSubscription(this)
434+
this.renderer.setSubscription(this)
451435
}
452436

453437
unsubscribe() {
@@ -471,8 +455,8 @@ class RenderMany<P> implements Subscription {
471455
const { component, props } = this.newProps
472456
const renderCache = render(component, props, this._values)
473457

474-
if (!structEq(this.renderCache.current, renderCache)) {
475-
this.setRenderCache(renderCache)
458+
if (!structEq(this.renderer.renderCache, renderCache)) {
459+
this.renderer.setRenderCache(renderCache)
476460
}
477461
}
478462

@@ -489,7 +473,7 @@ class RenderMany<P> implements Subscription {
489473
if (this._innerSubscriptions[i])
490474
return
491475

492-
this.setSubscription(null)
476+
this.renderer.setSubscription(null)
493477
}
494478
}
495479

0 commit comments

Comments
 (0)