|
| 1 | +/** |
| 2 | + * @type |
| 3 | + * @member type - The type of the element. Can be a string (for HTML/SVG tags), a function component (a function that returns a React element), or a class component (a class that extends React.Component and has a render() method). |
| 4 | + * @member props - An object containing the properties passed to the element. These are the properties that are specified in JSX |
| 5 | + * @member key - An optional key that can be used to identify an element in a list. This helps React determine which elements have changed when rendering a list. |
| 6 | + * @member ref - An optional ref that can be used to get a reference to the element's underlying DOM node. This is useful for imperatively manipulating the DOM |
| 7 | + * @member _owner - A reference to the component that created this element. This property is used for debugging purposes only and is not meant to be used in production code. |
| 8 | + */ |
| 9 | + |
| 10 | +interface ReactElement { |
| 11 | + type: string | Function; |
| 12 | + props: { |
| 13 | + [key: string]: any; |
| 14 | + children: ReactElement[]; |
| 15 | + }; |
| 16 | + key: string | null; |
| 17 | + ref: any; |
| 18 | + _owner: FiberNode | null; |
| 19 | +} |
| 20 | + |
| 21 | +/** |
| 22 | + * @type |
| 23 | + * @member element - This property holds the current element that the fiber represents in the render tree. |
| 24 | + * @member parent - This property holds a reference to the parent fiber of the current fiber. |
| 25 | + * @member child - This property holds a reference to the first child fiber of the current fiber. |
| 26 | + * @member sibling - This property holds a reference to the next sibling fiber of the current fiber. |
| 27 | + * @member alternate - This property holds a reference to the corresponding fiber in the previous render (i.e. the "old" fiber). |
| 28 | + * @member effectTag - This property is used to describe the type of update that needs to be made to the DOM when the fiber is committed. It can be one of three values: "PLACEMENT" (for new elements), "UPDATE" (for updates to existing elements), or "DELETION" (for elements that need to be removed from the DOM). |
| 29 | + * @member effects - This property is used to keep track of all the fibers that need to be updated, added, or removed from the DOM during the current commit phase. It is an array of fibers that have been marked with an effectTag other than null. |
| 30 | + */ |
| 31 | + |
| 32 | +interface FiberNode { |
| 33 | + element: ReactElement | null; |
| 34 | + parent: FiberNode | null; |
| 35 | + child: FiberNode | null; |
| 36 | + sibling: FiberNode | null; |
| 37 | + alternate: FiberNode | null; |
| 38 | + effectTag: string | null; |
| 39 | + effects?: FiberNode[]; |
| 40 | + stateNode: any; |
| 41 | +} |
| 42 | + |
| 43 | +/** |
| 44 | + * @function createFiberNode - Responsible for creating a new Fiber Node. A Fiber node is an object that represents a React element in the reconciliation process. It contains information about the element, its parent, and its children as well other data |
| 45 | + * @param element - React element that represents the component or DOM node that the fiber will represent. It is an object that contains information about the type of element, its props, its children, etc. |
| 46 | + * @param parent - The fiber node that represents the parent component of the element. It is used to link the new fiber node to the fiber tree by setting its parent property to the parent fiber node |
| 47 | + * @returns |
| 48 | + */ |
| 49 | +const createFiberNode = (element: ReactElement, parent: FiberNode | null): FiberNode => { |
| 50 | + return { |
| 51 | + element, |
| 52 | + parent, |
| 53 | + child: null, |
| 54 | + sibling: null, |
| 55 | + alternate: null, |
| 56 | + effectTag: null, |
| 57 | + stateNode: null, |
| 58 | + }; |
| 59 | +}; |
| 60 | + |
| 61 | +/** |
| 62 | + * @function reconcile - The purpose of the reconcile function is to reconcile the children array with the parentFiber node. In other words, it creates a fiber tree that reflects the current state of the React element tree |
| 63 | + * @param parentFiber - The fiber node of the parent element in the tree |
| 64 | + * @param children - An array of child elements (React elements) that belong to the parent element |
| 65 | + */ |
| 66 | +const reconcile = (parentFiber: FiberNode, children: any[]): void => { |
| 67 | + // checks to see if the parent fiber has an alternate fiber and if it does, it sets oldFiber to its child |
| 68 | + // If there is an alternate fiber for the parent fiber, it means that there was a previous version of the fiber tree that was reconciled, and oldFiber is set to its child fiber so that it can compared to the new children in the current version of the tree |
| 69 | + let oldFiber = parentFiber.alternate && parentFiber.alternate.child; |
| 70 | + let newFiber: FiberNode | null = null; |
| 71 | + let index = 0; // used to keep track of the current index in the children array that is being processed |
| 72 | + let prevFiber: FiberNode | null = null; // used to keep track of the last fiber that was process in the loop |
| 73 | + |
| 74 | + while (index < children.length || !oldFiber) { |
| 75 | + const reactElement = children[index]; |
| 76 | + // see if same type |
| 77 | + let sameType = oldFiber && reactElement && reactElement.type === oldFiber.element.type; |
| 78 | + // if same type is found, create a newFiber |
| 79 | + if (sameType) { |
| 80 | + newFiber = { |
| 81 | + element: reactElement, |
| 82 | + child: null, |
| 83 | + sibling: null, |
| 84 | + parent: parentFiber, |
| 85 | + alternate: oldFiber, |
| 86 | + effectTag: 'UPDATE', |
| 87 | + stateNode: oldFiber.stateNode, |
| 88 | + }; |
| 89 | + } else { |
| 90 | + if (reactElement) { |
| 91 | + newFiber = createFiberNode(reactElement, parentFiber); |
| 92 | + newFiber.effectTag = 'PLACEMENT'; |
| 93 | + |
| 94 | + if (typeof reactElement.type === 'function') { |
| 95 | + newFiber.stateNode = new (reactElement.type as any)(reactElement.props); |
| 96 | + } else { |
| 97 | + newFiber.stateNode = document.createElement(reactElement.type as string); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + if (oldFiber) { |
| 102 | + oldFiber.effectTag = 'DELETION'; |
| 103 | + parentFiber.effects = parentFiber.effects || []; |
| 104 | + parentFiber.effects.push(oldFiber); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + oldFiber === null ? null : oldFiber.sibling; |
| 109 | + // this block of code if responsible for creating a singly linked list of child nodes for the parent element, where each node has a child property pointing to its first child node and a sibling property pointing to its next sibling |
| 110 | + if (index === 0) { |
| 111 | + parentFiber.child = newFiber; |
| 112 | + } else if (reactElement) { |
| 113 | + prevFiber!.sibling = newFiber; |
| 114 | + } |
| 115 | + |
| 116 | + prevFiber = newFiber; |
| 117 | + index++; |
| 118 | + } |
| 119 | +}; |
| 120 | + |
| 121 | +/** |
| 122 | + * @function commitRoot - Responsible for committing the changes made to the DOM during the reconcilation phase |
| 123 | + * @param rootFiber - Root of the fiber tree. It contains information about the element and changes that need to be made to the DOM |
| 124 | + */ |
| 125 | +const commitRoot = (rootFiber: FiberNode) => { |
| 126 | + if (!rootFiber) return; |
| 127 | + rootFiber.effects?.forEach((fiber) => { |
| 128 | + if (fiber.effectTag === 'PLACEMENT') { |
| 129 | + // place existing element |
| 130 | + } else if (fiber.effectTag === 'UPDATE') { |
| 131 | + // update existing element |
| 132 | + } else if (fiber.effectTag === 'DELETION') { |
| 133 | + // remove element from DOM |
| 134 | + } |
| 135 | + }); |
| 136 | +}; |
| 137 | + |
| 138 | +/** |
| 139 | + * @function render - Creates a root fiber which is the top-level fiber that represents the root of the React element ree. |
| 140 | + * @param element - The react element that you want to render into the container. It represents the root of your component tree |
| 141 | + * @param container - The DOM element that you want to render your React element into. It is typically a <div> |
| 142 | + */ |
| 143 | +const render = (element: ReactElement, container: HTMLElement) => { |
| 144 | + const rootFiber: FiberNode = createFiberNode(element, null); |
| 145 | + rootFiber.element = container as any as ReactElement; // cast container to ReactElement |
| 146 | + const workInProgressRootFiber: FiberNode = { |
| 147 | + ...rootFiber, |
| 148 | + alternate: rootFiber, |
| 149 | + }; |
| 150 | + reconcile(workInProgressRootFiber, element.props.children); |
| 151 | + commitRoot(workInProgressRootFiber); |
| 152 | +}; |
0 commit comments