Skip to content

Commit 731c83e

Browse files
committed
docs(core): add detailed comments to rendering methods
Adds detailed comments to the , , and methods in . The new comments explain the purpose of these methods, their parameters, and the logic behind the implementation. This improves the readability and maintainability of the code.
1 parent ff7a59e commit 731c83e

File tree

1 file changed

+86
-11
lines changed

1 file changed

+86
-11
lines changed

src/core.node.ts

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -515,51 +515,87 @@ export class BaseNode<E extends NodeElementType> implements NodeInstance<E> {
515515
}
516516

517517
/**
518-
* A wrapper component that executes a function-as-a-child and processes its return value.
518+
* A special internal React component used to render "function-as-a-child" (render prop) patterns.
519+
* When a `BaseNode` receives a function as its `children` prop, it wraps that function
520+
* inside this `_functionRenderer` component. This component then executes the render function
521+
* and processes its return value, normalizing it into a renderable ReactNode.
522+
*
523+
* This allows `BaseNode` to support render props while maintaining its internal processing
524+
* and normalization logic for the dynamically generated content.
519525
* @method _functionRenderer
526+
* @param {Object} props The properties passed to the renderer.
527+
* @param {Function} props.render The function-as-a-child to execute.
528+
* @param {boolean} [props.disableEmotion] Inherited flag to disable Emotion styling for children.
529+
* @returns {ReactNode | null | undefined} The processed and rendered output of the render function.
520530
*/
521-
private static _functionRenderer<E extends ReactNode | NodeInstance>({ render, disableEmotion }: FunctionRendererProps<E>) {
531+
private static _functionRenderer<E extends ReactNode | NodeInstance>({ render, disableEmotion }: FunctionRendererProps<E>): ReactNode | null | undefined {
522532
let result: NodeElement
523533
try {
534+
// Execute the render prop function to get its output.
524535
result = render()
525536
} catch (error) {
526537
if (__DEV__) {
527538
console.error('MeoNode: Error executing function-as-a-child.', error)
528539
}
540+
// If the render function throws, treat its output as null to prevent crashes.
529541
result = null
530542
}
531-
if (result === null || result === undefined) return result
543+
544+
// Handle null or undefined results directly, as they are valid React render outputs.
545+
if (result === null || result === undefined) return result as never
546+
547+
// If the result is already a BaseNode instance, process it.
532548
if (isNodeInstance(result)) {
549+
// If emotion is disabled for the parent and not explicitly re-enabled on the child,
550+
// create a new BaseNode with emotion disabled and render it.
533551
if (disableEmotion && !result.rawProps.disableEmotion) return new BaseNode(result.element, { ...result.rawProps, disableEmotion: true }).render()
552+
// Otherwise, render the existing BaseNode directly.
534553
return result.render()
535554
}
555+
556+
// If the result is an array, it likely contains multiple children.
536557
if (Array.isArray(result)) {
558+
// Helper to generate a stable key for array items, crucial for React's reconciliation.
537559
const safeGetKey = (item: any, index: number) => {
538560
try {
561+
// Attempt to get a meaningful name for the element type.
539562
return `${getElementTypeName(item)}-${index}`
540563
} catch (error) {
541564
if (__DEV__) {
542565
console.error('MeoNode: Could not determine element type name for key in function-as-a-child.', error)
543566
}
567+
// Fallback to a generic key if type name cannot be determined.
544568
return `item-${index}`
545569
}
546570
}
547-
571+
// Map over the array, processing each item and assigning a key.
548572
return result.map((item, index) =>
549573
BaseNode._renderProcessedNode({ processedElement: BaseNode._processRawNode(item, disableEmotion), passedKey: safeGetKey(item, index) }),
550574
)
551575
}
552576
if (result instanceof React.Component) {
553577
return BaseNode._renderProcessedNode({ processedElement: BaseNode._processRawNode(result.render(), disableEmotion), disableEmotion })
554578
}
579+
580+
// Handle primitive types directly, as they are valid React children.
555581
if (typeof result === 'string' || typeof result === 'number' || typeof result === 'boolean') return result
582+
583+
// For any other non-primitive, non-array result, process it as a single NodeElement.
556584
const processedResult = BaseNode._processRawNode(result as NodeElement, disableEmotion)
585+
// If processing yields a valid element, render it.
557586
if (processedResult) return BaseNode._renderProcessedNode({ processedElement: processedResult, disableEmotion })
558-
return result
587+
// Fallback: return the original result if it couldn't be processed into a renderable node.
588+
return result as ReactNode
559589
}
560590

561591
/**
562-
* A legacy helper for the recursive child processing path. This is primarily used by `_functionRenderer`.
592+
* Renders a processed `NodeElement` into a ReactNode.
593+
* This helper is primarily used by `_functionRenderer` to handle the output of render props,
594+
* ensuring that `BaseNode` instances are correctly rendered and other React elements or primitives
595+
* are passed through. It also applies `disableEmotion` and `key` props as needed.
596+
*
597+
* This method is part of the child processing pipeline, converting internal `NodeElement` representations
598+
* into actual React elements that can be rendered by React.
563599
* @method _renderProcessedNode
564600
*/
565601
private static _renderProcessedNode({
@@ -571,18 +607,34 @@ export class BaseNode<E extends NodeElementType> implements NodeInstance<E> {
571607
passedKey?: string
572608
disableEmotion?: boolean
573609
}) {
610+
// Initialize an object to hold common props that might be applied to the new BaseNode.
574611
const commonBaseNodeProps: Partial<NodeProps<any>> = {}
612+
// If a `passedKey` is provided, add it to `commonBaseNodeProps`.
613+
// This key is typically used for React's reconciliation process.
575614
if (passedKey !== undefined) commonBaseNodeProps.key = passedKey
576615

616+
// If the processed element is already a BaseNode instance.
577617
if (isNodeInstance(processedElement)) {
618+
// Get the existing key from the raw props of the BaseNode.
578619
const existingKey = processedElement.rawProps?.key
620+
// Apply the `disableEmotion` flag to the raw props of the BaseNode.
579621
processedElement.rawProps.disableEmotion = disableEmotion
622+
// If the existing key is the same as the passed key, render the existing BaseNode directly.
623+
// This avoids unnecessary re-creation of the BaseNode instance.
580624
if (existingKey === passedKey) return processedElement.render()
625+
// Otherwise, create a new BaseNode instance, merging existing raw props with common props, then render it.
581626
return new BaseNode(processedElement.element, { ...processedElement.rawProps, ...commonBaseNodeProps }).render()
582627
}
628+
// If the processed element is a React class component (e.g., `class MyComponent extends React.Component`).
629+
// Create a new BaseNode for it, applying common props and `disableEmotion`, then render.
583630
if (isReactClassComponent(processedElement)) return new BaseNode(processedElement, { ...commonBaseNodeProps, disableEmotion }).render()
631+
// If the processed element is an instance of a React component (e.g., `new MyComponent()`).
632+
// Directly call its `render` method.
584633
if (processedElement instanceof React.Component) return processedElement.render()
634+
// If the processed element is a function (likely a functional component or a render prop that returned a component type).
635+
// Create a React element directly using `createElement`, passing the `passedKey`.
585636
if (typeof processedElement === 'function') return createElement(processedElement as ElementType, { key: passedKey })
637+
// For any other type (primitives, null, undefined, etc.), return it as a ReactNode.
586638
return processedElement as ReactNode
587639
}
588640

@@ -603,72 +655,94 @@ export class BaseNode<E extends NodeElementType> implements NodeInstance<E> {
603655
* @method render
604656
*/
605657
public render(parentBlocked: boolean = false): ReactElement<FinalNodeProps> {
658+
// A stable cache key derived from the element + important props signature.
606659
const cacheKey = this._stableKey
607660

608-
// Skip cache lookup on server-side
609-
// Server side rendering is always a fresh render, no cached elements should exist
661+
// On server we never reuse cached elements because that can cause hydration mismatches.
610662
const cacheEntry = BaseNode._isServer ? undefined : BaseNode._elementCache.get(cacheKey)
611663

664+
// Decide whether this node (and its subtree) should update given dependency arrays.
612665
const shouldUpdate = BaseNode._shouldNodeUpdate(cacheEntry?.prevDeps, this._deps, parentBlocked)
613666

667+
// Fast return: if nothing should update and we have a cached element, reuse it.
614668
if (!shouldUpdate && cacheEntry?.cachedElement) {
615669
return cacheEntry.cachedElement
616670
}
617671

672+
// When this node doesn't need update, its children are considered "blocked" and may be skipped.
618673
const childrenBlocked = !shouldUpdate
619674

675+
// Work stack for iterative, non-recursive traversal.
676+
// Each entry tracks the BaseNode, whether its children were pushed (isProcessed) and whether it is blocked.
620677
const workStack: { node: BaseNode<any>; isProcessed: boolean; blocked: boolean }[] = [{ node: this, isProcessed: false, blocked: childrenBlocked }]
678+
// Map to collect rendered React elements for processed BaseNode instances.
621679
const renderedElements = new Map<BaseNode<any>, ReactElement>()
622680

681+
// Iterative depth-first traversal with explicit begin/complete phases to avoid recursion.
623682
while (workStack.length > 0) {
624683
const currentWork = workStack[workStack.length - 1]
625684
const { node, isProcessed, blocked } = currentWork
626685

627686
if (!isProcessed) {
687+
// Begin phase: mark processed and push child BaseNodes onto the stack (in reverse order)
628688
currentWork.isProcessed = true
629689
const children = node.props.children
630690

631691
if (children) {
692+
// Only consider BaseNode children for further traversal; primitives and React elements are terminal.
632693
const childrenToProcess = (Array.isArray(children) ? children : [children]).filter(isNodeInstance)
633694

634695
for (let i = childrenToProcess.length - 1; i >= 0; i--) {
635696
const child = childrenToProcess[i]
636697
const childCacheKey = child._stableKey
637698

638-
// Skip cache lookup for children on server-side
699+
// Respect server/client differences for child cache lookup.
639700
const childCacheEntry = BaseNode._isServer ? undefined : BaseNode._elementCache.get(childCacheKey)
640701

702+
// Determine whether the child should update given its deps and the parent's blocked state.
641703
const childShouldUpdate = BaseNode._shouldNodeUpdate(childCacheEntry?.prevDeps, child._deps, blocked)
642704

705+
// If child doesn't need update and has cached element, reuse it immediately (no push).
643706
if (!childShouldUpdate && childCacheEntry?.cachedElement) {
644707
renderedElements.set(child, childCacheEntry.cachedElement)
645708
continue
646709
}
647710

711+
// Otherwise push child for processing; childBlocked inherits parent's blocked state.
648712
const childBlocked = blocked || !childShouldUpdate
649713
workStack.push({ node: child, isProcessed: false, blocked: childBlocked })
650714
}
651715
}
652716
} else {
717+
// Complete phase: all descendants have been processed; build this node's React element.
653718
workStack.pop()
654719

720+
// Extract node props. Non-present props default to undefined via destructuring.
655721
const { children: childrenInProps, key, css, nativeProps, disableEmotion, ...otherProps } = node.props
656722
let finalChildren: ReactNode[] = []
657723

658724
if (childrenInProps) {
725+
// Convert child placeholders into concrete React nodes:
726+
// - If it's a BaseNode, lookup its rendered ReactElement from the map.
727+
// - If it's already a React element, use it directly.
728+
// - Otherwise treat as primitive ReactNode.
659729
finalChildren = (Array.isArray(childrenInProps) ? childrenInProps : [childrenInProps]).map(child => {
660730
if (isNodeInstance(child)) return renderedElements.get(child)!
661731
if (isValidElement(child)) return child
662732
return child as ReactNode
663733
})
664734
}
665735

736+
// Merge element props: explicit other props + DOM native props + React key.
666737
const elementProps = { ...(otherProps as ComponentProps<ElementType>), key, ...nativeProps }
667738
let element: ReactElement<FinalNodeProps>
668739

740+
// Handle fragments specially: create fragment element with key and children.
669741
if (node.element === Fragment || isFragment(node.element)) {
670742
element = createElement(node.element as ExoticComponent<FragmentProps>, { key }, ...finalChildren)
671743
} else {
744+
// StyledRenderer for emotion-based styling unless explicitly disabled or no styles are present.
745+
// StyledRenderer handles SSR hydration and emotion CSS injection when css prop exists or element has style tags.
672746
const isStyledComponent = !disableEmotion && (css || !hasNoStyleTag(node.element))
673747
if (isStyledComponent) {
674748
element = createElement(StyledRenderer, { element: node.element, ...elementProps, css, suppressHydrationWarning: true }, ...finalChildren)
@@ -677,19 +751,20 @@ export class BaseNode<E extends NodeElementType> implements NodeInstance<E> {
677751
}
678752
}
679753

680-
// Only cache on client-side
681-
// Server-side cache would pollute and cause hydration mismatches
754+
// Cache the generated element on client-side to speed up future renders.
682755
if (!BaseNode._isServer) {
683756
BaseNode._elementCache.set(node._stableKey, {
684757
prevDeps: node._deps,
685758
cachedElement: element,
686759
})
687760
}
688761

762+
// Store the rendered element so parent nodes can reference it.
689763
renderedElements.set(node, element)
690764
}
691765
}
692766

767+
// Return the ReactElement corresponding to the root node (this).
693768
return renderedElements.get(this) as ReactElement<FinalNodeProps>
694769
}
695770

0 commit comments

Comments
 (0)