Skip to content

Commit

Permalink
Add useMutableSource and useOpaqueIdentifier hooks (#64)
Browse files Browse the repository at this point in the history
* Add useMutableSource hook

* Add useOpaqueIdentifier

* Memoize opaque identifiers

* Add useOpaqueIdentifier hook to Dispatcher
  • Loading branch information
kitten authored Feb 24, 2021
1 parent deb9b56 commit 2e4fda4
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 5 deletions.
26 changes: 21 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// @flow

import { type Node, type Element } from 'react'
import type { Visitor, YieldFrame, Frame, AbstractElement } from './types'
import type {
Visitor,
YieldFrame,
Frame,
AbstractElement,
RendererState
} from './types'
import { visit, update, SHOULD_YIELD } from './visitor'
import { getChildrenArray } from './element'

Expand All @@ -10,6 +16,8 @@ import {
setCurrentContextMap,
setCurrentErrorFrame,
getCurrentErrorFrame,
setCurrentRendererState,
initRendererState,
Dispatcher
} from './internals'

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

return Promise.resolve(frame.thenable).then(
() => {
setCurrentRendererState(state)
update(frame, queue, visitor)
return flushFrames(queue, visitor)
return flushFrames(queue, visitor, state)
},
(error: Error) => {
if (!frame.errorFrame) throw error
Expand All @@ -49,7 +62,10 @@ const renderPrepass = (element: Node, visitor?: Visitor): Promise<void> => {
if (!visitor) visitor = defaultVisitor

const queue: Frame[] = []

// Renderer state is kept globally but restored and
// passed around manually since it isn't dependent on the
// render tree
const state = initRendererState()
// Context state is kept globally and is modified in-place.
// Before we start walking the element tree we need to reset
// its current state
Expand All @@ -63,7 +79,7 @@ const renderPrepass = (element: Node, visitor?: Visitor): Promise<void> => {
return Promise.reject(error)
}

return flushFrames(queue, visitor)
return flushFrames(queue, visitor, state)
}

export default renderPrepass
25 changes: 25 additions & 0 deletions src/internals/dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
// Source: https://github.com/facebook/react/blob/c21c41e/packages/react-dom/src/server/ReactPartialRendererHooks.js

import { readContextValue } from './context'
import { rendererStateRef } from './state'
import is from './objectIs'

import type {
MutableSource,
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
AbstractContext,
BasicStateAction,
Dispatch,
Expand All @@ -14,6 +18,7 @@ import type {
} from '../types'

export opaque type Identity = {}
export opaque type OpaqueIDType = string

let currentIdentity: Identity | null = null

Expand Down Expand Up @@ -245,6 +250,15 @@ function useRef<T>(initialValue: T): { current: T } {
}
}

function useOpaqueIdentifier(): OpaqueIDType {
getCurrentIdentity()
workInProgressHook = createWorkInProgressHook()
if (!workInProgressHook.memoizedState)
workInProgressHook.memoizedState =
'R:' + (rendererStateRef.current.uniqueID++).toString(36)
return workInProgressHook.memoizedState
}

function dispatchAction<A>(
componentIdentity: Identity,
queue: UpdateQueue<A>,
Expand Down Expand Up @@ -284,6 +298,15 @@ function useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
return useMemo(() => callback, deps)
}

function useMutableSource<Source, Snapshot>(
source: MutableSource<Source>,
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
_subscribe: MutableSourceSubscribeFn<Source, Snapshot>
): Snapshot {
getCurrentIdentity()
return getSnapshot(source._source)
}

function noop(): void {}

function useTransition(): [(callback: () => void) => void, boolean] {
Expand All @@ -305,8 +328,10 @@ export const Dispatcher = {
useRef,
useState,
useCallback,
useMutableSource,
useTransition,
useDeferredValue,
useOpaqueIdentifier,
// ignore useLayout effect completely as usage of it will be caught
// in a subsequent render pass
useLayoutEffect: noop,
Expand Down
1 change: 1 addition & 0 deletions src/internals/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

export * from './context'
export * from './error'
export * from './state'
export * from './dispatcher'
12 changes: 12 additions & 0 deletions src/internals/state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @flow

import type { RendererState } from '../types'

/** The current global renderer state per render cycle */
export const rendererStateRef: {| current: RendererState |} = {
current: { uniqueID: 0 }
}
export const initRendererState = (): RendererState =>
(rendererStateRef.current = { uniqueID: 0 })
export const setCurrentRendererState = (state: RendererState) =>
(rendererStateRef.current = state)
14 changes: 14 additions & 0 deletions src/types/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,17 @@ export type AbstractElement =
| DOMElement
| PortalElement
| SuspenseElement

export type MutableSourceGetSnapshotFn<
Source: $NonMaybeType<mixed>,
Snapshot
> = (source: Source) => Snapshot

export type MutableSourceSubscribeFn<Source: $NonMaybeType<mixed>, Snapshot> = (
source: Source,
callback: (snapshot: Snapshot) => void
) => () => void

export type MutableSource<Source: $NonMaybeType<mixed>> = {
_source: Source
}
4 changes: 4 additions & 0 deletions src/types/frames.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ export type YieldFrame = BaseFrame & {
}

export type Frame = ClassFrame | HooksFrame | LazyFrame | YieldFrame

export type RendererState = {|
uniqueID: number
|}

0 comments on commit 2e4fda4

Please sign in to comment.