Skip to content

Commit 2e4fda4

Browse files
authored
Add useMutableSource and useOpaqueIdentifier hooks (#64)
* Add useMutableSource hook * Add useOpaqueIdentifier * Memoize opaque identifiers * Add useOpaqueIdentifier hook to Dispatcher
1 parent deb9b56 commit 2e4fda4

File tree

6 files changed

+77
-5
lines changed

6 files changed

+77
-5
lines changed

src/index.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
// @flow
22

33
import { type Node, type Element } from 'react'
4-
import type { Visitor, YieldFrame, Frame, AbstractElement } from './types'
4+
import type {
5+
Visitor,
6+
YieldFrame,
7+
Frame,
8+
AbstractElement,
9+
RendererState
10+
} from './types'
511
import { visit, update, SHOULD_YIELD } from './visitor'
612
import { getChildrenArray } from './element'
713

@@ -10,6 +16,8 @@ import {
1016
setCurrentContextMap,
1117
setCurrentErrorFrame,
1218
getCurrentErrorFrame,
19+
setCurrentRendererState,
20+
initRendererState,
1321
Dispatcher
1422
} from './internals'
1523

@@ -18,7 +26,11 @@ import {
1826
the queue. Hence we recursively look at suspended components in
1927
this queue, wait for their promises to resolve, and continue
2028
calling visit() on their children. */
21-
const flushFrames = (queue: Frame[], visitor: Visitor): Promise<void> => {
29+
const flushFrames = (
30+
queue: Frame[],
31+
visitor: Visitor,
32+
state: RendererState
33+
): Promise<void> => {
2234
const frame = queue.shift()
2335
if (!frame) {
2436
return Promise.resolve()
@@ -32,8 +44,9 @@ const flushFrames = (queue: Frame[], visitor: Visitor): Promise<void> => {
3244

3345
return Promise.resolve(frame.thenable).then(
3446
() => {
47+
setCurrentRendererState(state)
3548
update(frame, queue, visitor)
36-
return flushFrames(queue, visitor)
49+
return flushFrames(queue, visitor, state)
3750
},
3851
(error: Error) => {
3952
if (!frame.errorFrame) throw error
@@ -49,7 +62,10 @@ const renderPrepass = (element: Node, visitor?: Visitor): Promise<void> => {
4962
if (!visitor) visitor = defaultVisitor
5063

5164
const queue: Frame[] = []
52-
65+
// Renderer state is kept globally but restored and
66+
// passed around manually since it isn't dependent on the
67+
// render tree
68+
const state = initRendererState()
5369
// Context state is kept globally and is modified in-place.
5470
// Before we start walking the element tree we need to reset
5571
// its current state
@@ -63,7 +79,7 @@ const renderPrepass = (element: Node, visitor?: Visitor): Promise<void> => {
6379
return Promise.reject(error)
6480
}
6581

66-
return flushFrames(queue, visitor)
82+
return flushFrames(queue, visitor, state)
6783
}
6884

6985
export default renderPrepass

src/internals/dispatcher.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
// Source: https://github.com/facebook/react/blob/c21c41e/packages/react-dom/src/server/ReactPartialRendererHooks.js
33

44
import { readContextValue } from './context'
5+
import { rendererStateRef } from './state'
56
import is from './objectIs'
67

78
import type {
9+
MutableSource,
10+
MutableSourceGetSnapshotFn,
11+
MutableSourceSubscribeFn,
812
AbstractContext,
913
BasicStateAction,
1014
Dispatch,
@@ -14,6 +18,7 @@ import type {
1418
} from '../types'
1519

1620
export opaque type Identity = {}
21+
export opaque type OpaqueIDType = string
1722

1823
let currentIdentity: Identity | null = null
1924

@@ -245,6 +250,15 @@ function useRef<T>(initialValue: T): { current: T } {
245250
}
246251
}
247252

253+
function useOpaqueIdentifier(): OpaqueIDType {
254+
getCurrentIdentity()
255+
workInProgressHook = createWorkInProgressHook()
256+
if (!workInProgressHook.memoizedState)
257+
workInProgressHook.memoizedState =
258+
'R:' + (rendererStateRef.current.uniqueID++).toString(36)
259+
return workInProgressHook.memoizedState
260+
}
261+
248262
function dispatchAction<A>(
249263
componentIdentity: Identity,
250264
queue: UpdateQueue<A>,
@@ -284,6 +298,15 @@ function useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
284298
return useMemo(() => callback, deps)
285299
}
286300

301+
function useMutableSource<Source, Snapshot>(
302+
source: MutableSource<Source>,
303+
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
304+
_subscribe: MutableSourceSubscribeFn<Source, Snapshot>
305+
): Snapshot {
306+
getCurrentIdentity()
307+
return getSnapshot(source._source)
308+
}
309+
287310
function noop(): void {}
288311

289312
function useTransition(): [(callback: () => void) => void, boolean] {
@@ -305,8 +328,10 @@ export const Dispatcher = {
305328
useRef,
306329
useState,
307330
useCallback,
331+
useMutableSource,
308332
useTransition,
309333
useDeferredValue,
334+
useOpaqueIdentifier,
310335
// ignore useLayout effect completely as usage of it will be caught
311336
// in a subsequent render pass
312337
useLayoutEffect: noop,

src/internals/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
export * from './context'
44
export * from './error'
5+
export * from './state'
56
export * from './dispatcher'

src/internals/state.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @flow
2+
3+
import type { RendererState } from '../types'
4+
5+
/** The current global renderer state per render cycle */
6+
export const rendererStateRef: {| current: RendererState |} = {
7+
current: { uniqueID: 0 }
8+
}
9+
export const initRendererState = (): RendererState =>
10+
(rendererStateRef.current = { uniqueID: 0 })
11+
export const setCurrentRendererState = (state: RendererState) =>
12+
(rendererStateRef.current = state)

src/types/element.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,17 @@ export type AbstractElement =
180180
| DOMElement
181181
| PortalElement
182182
| SuspenseElement
183+
184+
export type MutableSourceGetSnapshotFn<
185+
Source: $NonMaybeType<mixed>,
186+
Snapshot
187+
> = (source: Source) => Snapshot
188+
189+
export type MutableSourceSubscribeFn<Source: $NonMaybeType<mixed>, Snapshot> = (
190+
source: Source,
191+
callback: (snapshot: Snapshot) => void
192+
) => () => void
193+
194+
export type MutableSource<Source: $NonMaybeType<mixed>> = {
195+
_source: Source
196+
}

src/types/frames.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,7 @@ export type YieldFrame = BaseFrame & {
4747
}
4848

4949
export type Frame = ClassFrame | HooksFrame | LazyFrame | YieldFrame
50+
51+
export type RendererState = {|
52+
uniqueID: number
53+
|}

0 commit comments

Comments
 (0)