From 3debe0255abd1a1db67d23f5b7375243cff4a91e Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Thu, 22 Dec 2022 13:43:06 +0100 Subject: [PATCH] Use type guards for internal types --- src/create-context.js | 2 +- src/diff/catch-error.js | 6 ++--- src/diff/children.js | 9 ++++--- src/diff/mount.js | 17 ++++++------- src/diff/patch.js | 2 +- src/helpers.js | 16 +++++++++++++ src/internal.d.ts | 53 ++++++++++++++++++++++++++++++++--------- src/tree.js | 19 +++++++++------ 8 files changed, 86 insertions(+), 38 deletions(-) create mode 100644 src/helpers.js diff --git a/src/create-context.js b/src/create-context.js index ccb0a67885..dbb0135af1 100644 --- a/src/create-context.js +++ b/src/create-context.js @@ -4,7 +4,7 @@ let nextContextId = 0; const providers = new Set(); -/** @param {import('./internal').Internal} internal */ +/** @param {import('./internal').ComponentInternal} internal */ export const unsubscribeFromContext = internal => { // if this was a context provider, delete() returns true and we exit: if (providers.delete(internal)) return; diff --git a/src/diff/catch-error.js b/src/diff/catch-error.js index 127899e66c..9953c8eaf6 100644 --- a/src/diff/catch-error.js +++ b/src/diff/catch-error.js @@ -1,10 +1,10 @@ import { DIRTY_BIT, MODE_RERENDERING_ERROR, - MODE_PENDING_ERROR, - TYPE_COMPONENT + MODE_PENDING_ERROR } from '../constants'; import { ENABLE_CLASSES } from '../component'; +import { isComponentInternal } from '../helpers'; /** * Find the closest error boundary to a thrown error and call it @@ -16,7 +16,7 @@ import { ENABLE_CLASSES } from '../component'; export function _catchError(error, internal) { while ((internal = internal._parent)) { if ( - internal.flags & TYPE_COMPONENT && + isComponentInternal(internal) && ~internal.flags & MODE_RERENDERING_ERROR ) { try { diff --git a/src/diff/children.js b/src/diff/children.js index 091a6765f8..0636cb9f65 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -1,17 +1,16 @@ import { applyRef } from './refs'; import { normalizeToVNode } from '../create-element'; import { - TYPE_COMPONENT, MODE_HYDRATE, MODE_SUSPENDED, EMPTY_ARR, - TYPE_DOM, UNDEFINED } from '../constants'; import { mount } from './mount'; import { patch } from './patch'; import { unmount } from './unmount'; import { createInternal, getDomSibling } from '../tree'; +import { isComponentInternal, isDomInternal } from '../helpers'; /** * Update an internal with new children. @@ -98,7 +97,7 @@ export function patchChildren(internal, children, parentDom) { } // Perform insert of new dom - if (childInternal.flags & TYPE_DOM) { + if (isDomInternal(childInternal)) { parentDom.insertBefore( childInternal.data, getDomSibling(internal, skewedIndex) @@ -132,7 +131,7 @@ export function patchChildren(internal, children, parentDom) { if (matchingIndex == i) break go; let nextSibling = getDomSibling(internal, skewedIndex + 1); - if (childInternal.flags & TYPE_DOM) { + if (isDomInternal(childInternal)) { parentDom.insertBefore(childInternal.data, nextSibling); } else { insertComponentDom(childInternal, nextSibling, parentDom); @@ -241,7 +240,7 @@ export function insertComponentDom(internal, nextSibling, parentDom) { if (childInternal) { childInternal._parent = internal; - if (childInternal.flags & TYPE_COMPONENT) { + if (isComponentInternal(childInternal)) { insertComponentDom(childInternal, nextSibling, parentDom); } else if (childInternal.data != nextSibling) { parentDom.insertBefore(childInternal.data, nextSibling); diff --git a/src/diff/mount.js b/src/diff/mount.js index ee89293f98..46e4ac4758 100644 --- a/src/diff/mount.js +++ b/src/diff/mount.js @@ -1,6 +1,5 @@ import { applyRef } from './refs'; import { - TYPE_COMPONENT, TYPE_ELEMENT, MODE_HYDRATE, MODE_MUTATIVE_HYDRATE, @@ -9,7 +8,6 @@ import { TYPE_TEXT, TYPE_CLASS, MODE_ERRORED, - TYPE_ROOT, MODE_SVG, DIRTY_BIT } from '../constants'; @@ -19,6 +17,7 @@ import { createInternal, getParentContext } from '../tree'; import options from '../options'; import { ENABLE_CLASSES } from '../component'; import { commitQueue } from './commit'; +import { isComponentInternal, isRootInternal } from '../helpers'; /** * Diff two virtual nodes and apply proper changes to the DOM * @param {import('../internal').Internal} internal The Internal node to mount @@ -34,14 +33,11 @@ export function mount(internal, newVNode, parentDom, startDom) { let nextDomSibling, prevStartDom; try { - if (internal.flags & TYPE_COMPONENT) { + if (isComponentInternal(internal)) { // Root nodes signal that an attempt to render into a specific DOM node on // the page. Root nodes can occur anywhere in the tree and not just at the // top. - if ( - internal.flags & TYPE_ROOT && - newVNode.props._parentDom !== parentDom - ) { + if (isRootInternal(internal) && newVNode.props._parentDom !== parentDom) { parentDom = newVNode.props._parentDom; prevStartDom = startDom; startDom = null; @@ -59,6 +55,7 @@ export function mount(internal, newVNode, parentDom, startDom) { ); } + // if ( internal.data._commitCallbacks && internal.data._commitCallbacks.length @@ -249,7 +246,7 @@ export function mountChildren(internal, children, parentDom, startDom) { newDom = childInternal.data; - if (childInternal.flags & TYPE_COMPONENT || newDom == startDom) { + if (isComponentInternal(childInternal) || newDom == startDom) { // If the child is a Fragment-like or if it is DOM VNode and its _dom // property matches the dom we are diffing (i.e. startDom), just // continue with the mountedNextChild @@ -290,14 +287,14 @@ export function mountChildren(internal, children, parentDom, startDom) { } /** - * @param {import('../internal').Internal} internal The component's backing Internal node + * @param {import('../internal').ComponentInternal} internal The component's backing Internal node * @param {import('../internal').PreactNode} startDom the preceding node * @returns {import('../internal').PreactNode} the component's children */ function mountComponent(internal, startDom) { /** @type {import('../internal').Component} */ let c; - let type = /** @type {import('../internal').ComponentType} */ (internal.type); + let type = internal.type; let newProps = internal.props; // Necessary for createContext api. Setting this property will pass diff --git a/src/diff/patch.js b/src/diff/patch.js index 8398e4ed22..5c6699e76f 100644 --- a/src/diff/patch.js +++ b/src/diff/patch.js @@ -192,7 +192,7 @@ function patchElement(internal, vnode) { } /** - * @param {import('../internal').Internal} internal The component's backing Internal node + * @param {import('../internal').ComponentInternal} internal The component's backing Internal node * @param {import('../internal').VNode} newVNode The new virtual node * @returns {import('../internal').ComponentChildren} the component's children */ diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000000..a283ddb895 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,16 @@ +import { TYPE_COMPONENT, TYPE_DOM, TYPE_ELEMENT, TYPE_ROOT } from './constants'; + +/** @type {import('./internal').isComponentInternal} */ +export const isComponentInternal = internal => + /** @type {*} */ (internal.flags & TYPE_COMPONENT); + +/** @type {import('./internal').isDomInternal} */ +export const isDomInternal = internal => + /** @type {*} */ (internal.flags & TYPE_DOM); + +/** @type {import('./internal').isRootInternal} */ +export const isRootInternal = internal => + /** @type {*} */ (internal.flags & TYPE_ROOT); + +/** @type {(internal: import('./internal').Internal) => number} */ +export const isElementInternal = internal => internal.flags & TYPE_ELEMENT; diff --git a/src/internal.d.ts b/src/internal.d.ts index bf4328669b..d2bbd1b4f0 100644 --- a/src/internal.d.ts +++ b/src/internal.d.ts @@ -45,7 +45,7 @@ export interface Options extends preact.Options { _internal?(internal: Internal, vnode: VNode | string): void; } -export type CommitQueue = Internal[]; +export type CommitQueue = ComponentInternal[]; // Redefine ComponentFactory using our new internal FunctionalComponent interface above export type ComponentFactory

= @@ -130,18 +130,13 @@ export interface VNode

extends preact.VNode

{ * An Internal is a persistent backing node within Preact's virtual DOM tree. * Think of an Internal like a long-lived VNode with stored data and tree linkages. */ -export interface Internal

{ - type: string | ComponentType

; - /** The props object for Elements/Components, and the string contents for Text */ - props: (P & { children: ComponentChildren }) | string | number; +export interface BaseInternal

{ key: any; ref: Ref | null; _prevRef: Ref | null; /** Bitfield containing information about the Internal or its component. */ flags: number; - /** Polymorphic property to store extensions like hooks on */ - data: object | PreactNode; /** The function that triggers in-place re-renders for an internal */ rerender: (internal: Internal) => void; @@ -159,16 +154,52 @@ export interface Internal

{ _component: Component | null; /** This Internal's distance from the tree root */ _depth: number | null; - /** Callbacks to invoke when this internal commits */ - _commitCallbacks: Array<() => void>; - _stateCallbacks: Array<() => void>; // Only class components } +export interface ComponentInternal

extends BaseInternal

{ + type: ComponentType

; + props: P & { children: ComponentChildren }; + /** Polymorphic property to store extensions like hooks on */ + data: { + /** Callbacks to invoke when this internal commits */ + _commitCallbacks: Array<() => void>; + _stateCallbacks: Array<() => void>; // Only class components + [key: string]: any; + }; +} + +export interface RootInternal

+ extends Exclude, 'props'> { + props: P & { children: ComponentChildren; _parentDom: PreactNode }; +} + +export interface DomInternal

extends BaseInternal

{ + type: string; + /** The props object for Elements/Components, and the string contents for Text */ + props: (P & { children: ComponentChildren }) | string | number; + data: PreactNode; +} + +export type Internal

= + | ComponentInternal

+ | DomInternal

+ | RootInternal

; + +export type isDomInternal

= ( + internal: Internal

+) => internal is DomInternal

; +export type isComponentInternal

= ( + internal: Internal

+) => internal is ComponentInternal

; +export type isRootInternal

= ( + internal: Internal

+) => internal is RootInternal

; + export interface Component

extends preact.Component { // When component is functional component, this is reset to functional component constructor: ComponentType

; state: S; // Override Component["state"] to not be readonly for internal use, specifically Hooks - _internal?: Internal

| null; + _internal?: ComponentInternal

| null; _nextState?: S | null; // Only class components /** Only used in the devtools to later dirty check if state has changed */ _prevState?: S | null; diff --git a/src/tree.js b/src/tree.js index 24f6ba8b7a..442e6a7b7d 100644 --- a/src/tree.js +++ b/src/tree.js @@ -7,11 +7,16 @@ import { TYPE_ROOT, INHERITED_MODES, TYPE_COMPONENT, - TYPE_DOM, MODE_SVG, UNDEFINED } from './constants'; import { enqueueRender } from './component'; +import { + isComponentInternal, + isDomInternal, + isElementInternal, + isRootInternal +} from './helpers'; /** * Create an internal tree node @@ -106,8 +111,8 @@ export function createInternal(vnode, parentInternal) { } const shouldSearchComponent = internal => - internal.flags & TYPE_COMPONENT && - (!(internal.flags & TYPE_ROOT) || + isComponentInternal(internal) && + (!isRootInternal(internal) || internal.props._parentDom == getParentDom(internal._parent)); /** @@ -153,7 +158,7 @@ export function getChildDom(internal, offset) { for (; offset < internal._children.length; offset++) { let child = internal._children[offset]; if (child != null) { - if (child.flags & TYPE_DOM) { + if (isDomInternal(child)) { return child.data; } @@ -190,15 +195,15 @@ export function getParentDom(internal) { let parent = internal; // if this is a Root internal, return its parent DOM: - if (parent.flags & TYPE_ROOT) { + if (isRootInternal(parent)) { return parent.props._parentDom; } // walk up the tree to find the nearest DOM or Root Internal: while ((parent = parent._parent)) { - if (parent.flags & TYPE_ROOT) { + if (isRootInternal(parent)) { return parent.props._parentDom; - } else if (parent.flags & TYPE_ELEMENT) { + } else if (isElementInternal(parent)) { return parent.data; } }