diff --git a/src/index.js b/src/index.js index 186c6e8..585d5f7 100644 --- a/src/index.js +++ b/src/index.js @@ -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' @@ -10,6 +16,8 @@ import { setCurrentContextMap, setCurrentErrorFrame, getCurrentErrorFrame, + setCurrentRendererState, + initRendererState, Dispatcher } from './internals' @@ -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 => { +const flushFrames = ( + queue: Frame[], + visitor: Visitor, + state: RendererState +): Promise => { const frame = queue.shift() if (!frame) { return Promise.resolve() @@ -32,8 +44,9 @@ const flushFrames = (queue: Frame[], visitor: Visitor): Promise => { 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 @@ -49,7 +62,10 @@ const renderPrepass = (element: Node, visitor?: Visitor): Promise => { 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 @@ -63,7 +79,7 @@ const renderPrepass = (element: Node, visitor?: Visitor): Promise => { return Promise.reject(error) } - return flushFrames(queue, visitor) + return flushFrames(queue, visitor, state) } export default renderPrepass diff --git a/src/internals/dispatcher.js b/src/internals/dispatcher.js index d22e323..2fd89f2 100644 --- a/src/internals/dispatcher.js +++ b/src/internals/dispatcher.js @@ -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, @@ -14,6 +18,7 @@ import type { } from '../types' export opaque type Identity = {} +export opaque type OpaqueIDType = string let currentIdentity: Identity | null = null @@ -245,6 +250,15 @@ function useRef(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( componentIdentity: Identity, queue: UpdateQueue, @@ -284,6 +298,15 @@ function useCallback(callback: T, deps: Array | void | null): T { return useMemo(() => callback, deps) } +function useMutableSource( + source: MutableSource, + getSnapshot: MutableSourceGetSnapshotFn, + _subscribe: MutableSourceSubscribeFn +): Snapshot { + getCurrentIdentity() + return getSnapshot(source._source) +} + function noop(): void {} function useTransition(): [(callback: () => void) => void, boolean] { @@ -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, diff --git a/src/internals/index.js b/src/internals/index.js index 7e399e5..2b4ed46 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -2,4 +2,5 @@ export * from './context' export * from './error' +export * from './state' export * from './dispatcher' diff --git a/src/internals/state.js b/src/internals/state.js new file mode 100644 index 0000000..27d1a59 --- /dev/null +++ b/src/internals/state.js @@ -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) diff --git a/src/types/element.js b/src/types/element.js index 53151aa..1e1cf1f 100644 --- a/src/types/element.js +++ b/src/types/element.js @@ -180,3 +180,17 @@ export type AbstractElement = | DOMElement | PortalElement | SuspenseElement + +export type MutableSourceGetSnapshotFn< + Source: $NonMaybeType, + Snapshot +> = (source: Source) => Snapshot + +export type MutableSourceSubscribeFn, Snapshot> = ( + source: Source, + callback: (snapshot: Snapshot) => void +) => () => void + +export type MutableSource> = { + _source: Source +} diff --git a/src/types/frames.js b/src/types/frames.js index 8659753..8fa48cf 100644 --- a/src/types/frames.js +++ b/src/types/frames.js @@ -47,3 +47,7 @@ export type YieldFrame = BaseFrame & { } export type Frame = ClassFrame | HooksFrame | LazyFrame | YieldFrame + +export type RendererState = {| + uniqueID: number +|}