- createElement
- createRoot/render
- fiber 架构
- reconcile
- 函数组件和 useState
- 渲染

- 构建的fiber树

- 新增


- 删除

- 更新



// webpack 配置
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
bugfixes: true,
targets: {
chrome: '58',
ie: '11',
},
useBuiltIns: 'usage',
corejs: { version: '3.38.0', proposals: true },
},
],
['@babel/preset-react'],
'@babel/preset-typescript',
],
},
},
}
// 使用 pragma
import { createElement } from '../mini_react/react-dom.ts';
/** @jsx createElement */
/**
* 创建虚拟 DOM 元素
* @param type 标签
* @param props 属性
* @param children 后代元素
* @returns 虚拟 DOM 元素
*/
export function createElement(type: Fiber['type'], props: Record<string, unknown> | null, ...children: Fiber[]) {
return {
type,
props: {
...(props ?? {}),
children: children.flat(),
},
};
}
/**
* 创建一个 fiber 根用来渲染
* @param element 虚拟 DOM
* @returns 渲染函数
*/
export function createRoot(element: Fiber): { root: Fiber; render: (container: HTMLElement) => void } {
const root: Fiber = {
type: 'div',
dom: null,
props: {
children: [element],
},
alternate: miniReact.lastCommittedRoot,
};
return {
root,
render(container) {
miniReact.deletedFibers = [];
root.dom = container;
miniReact.workInProgressRoot = root;
miniReact.nextUnitOfWork = miniReact.workInProgressRoot;
},
};
}
export interface MiniReact {
/**
* 下一个 fiber 工作节点
*/
nextUnitOfWork: Fiber | null | undefined;
/**
* 上一次提交到真实 DOM 的 fiber 树的根引用
*/
lastCommittedRoot: Fiber | null | undefined;
/**
* 正在构建的 fiber 树的根引用
*/
workInProgressRoot: Fiber | null | undefined;
/**
* 正在构建的 fiber 节点
*/
workInProgressFiber: Fiber | null | undefined;
/**
* 需要删除的 fiber 节点
*/
deletedFibers: Fiber[];
}
export const miniReact: MiniReact = {
nextUnitOfWork: null,
lastCommittedRoot: null,
workInProgressRoot: null,
workInProgressFiber: null,
deletedFibers: [],
};
/**
* react 工作循环
*/
const workLoop: IdleRequestCallback = (deadline) => {
let shouldYield = false;
while (miniReact.nextUnitOfWork && !shouldYield) {
miniReact.nextUnitOfWork = performUnitOfWork(miniReact.nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 5;
}
if (!miniReact.nextUnitOfWork && miniReact.workInProgressRoot) {
commit();
}
window.requestIdleCallback(workLoop);
};
window.requestIdleCallback(workLoop);
/**
* Fiber 树节点类型
*/
export interface Fiber {
/**
* fiber 类型
* 如果是一个函数,则为函数组件
* 否则是一个 html 元素标签
*/
readonly type: 'TEXT' | keyof HTMLElementTagNameMap | ((props: Fiber['props']) => Fiber);
/**
* 属性
*/
props?: {
/**
* 后代虚拟DOM元素
*/
children: Fiber[];
} & Record<string, unknown>;
/**
* 对应的真实 DOM
*/
dom?: HTMLElement | Text | null;
/**
* 直接子节点
*/
child?: Fiber;
/**
* 直接父节点
*/
parent?: Fiber;
/**
* 直接兄弟节点
*/
sibling?: Fiber;
/**
* 若为文本节点,则存储文本内容
*/
textContent?: string;
/**
* use state hook
*/
useStateHooks?: UseStateHook[];
/**
* use state hook index
*/
useStateHookIndex?: number;
/**
* 可复用的 fiber
*/
alternate?: Fiber | null;
/**
* 当前 fiber 的来源类型
*/
tag?: 'UPDATE' | 'ADD' | 'DELETE';
}
/**
* 构建当前的 fiber 的节点,并返回下一个工作节点
* @param fiber fiber 树节点
* @returns 下一个 fiber 树工作节点
*/
function performUnitOfWork(fiber: Fiber) {
/**
* 函数组件类型的 fiber
*/
if (fiber.type instanceof Function) buildFunctionComponentFibers(fiber);
else buildHostComponentFibers(fiber);
return getNextFiberNode(fiber);
}
/**
* 更新
* @param fiber fiber 节点
*/
function buildFunctionComponentFibers(fiber: Fiber) {
miniReact.workInProgressFiber = fiber;
fiber.useStateHookIndex = 0;
miniReact.workInProgressFiber.useStateHooks = [];
const children = [(fiber.type as (props: Fiber['props']) => Fiber)(fiber.props)];
reconcileChildren(fiber, children);
}
function buildHostComponentFibers(fiber: Fiber) {
if (!fiber.dom) fiber.dom = createDOM(fiber);
reconcileChildren(fiber, fiber?.props?.children ?? []);
}
/**
* 选择下一个 fiber 工作节点
*/
function getNextFiberNode(fiber: Fiber) {
if (fiber.child) return fiber.child;
let nextFiber: Fiber | undefined = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
/**
* 提交 fiber root 上的所有离屏 DOM 到真实 DOM
*/
function commit() {
miniReact.deletedFibers.forEach(commitFiber);
commitFiber(miniReact.workInProgressRoot!.child);
miniReact.lastCommittedRoot = miniReact.workInProgressRoot;
miniReact.workInProgressRoot = null;
}
function commitFiber(fiber: Fiber | null | undefined) {
if (!fiber) return;
let domParentFiber = fiber.parent;
while (!domParentFiber?.dom) {
domParentFiber = domParentFiber?.parent;
}
const parentDOM = domParentFiber.dom;
if (!parentDOM) return;
if (fiber.tag === 'ADD' && fiber.dom != null) {
parentDOM.appendChild(fiber.dom);
} else if (fiber.tag === 'UPDATE' && fiber.dom != null) {
updateDom(fiber.dom, fiber?.alternate?.props, fiber.props);
} else if (fiber.tag === 'DELETE' && fiber.dom != null) {
commitDeletion(fiber, parentDOM);
}
commitFiber(fiber.child);
commitFiber(fiber.sibling);
}
function commitDeletion(fiber: Fiber | null | undefined, parentDOM: HTMLElement | Text) {
if (!fiber) return;
if (fiber.dom) {
parentDOM.removeChild(fiber.dom);
} else {
commitDeletion(fiber.child, parentDOM);
}
}
const isProperty = (key: string) => key !== 'children';
const isEvent = (key: string) => key.startsWith('on');
/**
* 根据传入fiber 树节点创建对应的 DOM
* @param fiber fiber 树节点
* @returns fiber 树节点对应的真实 DOM
*/
export function createDOM(fiber: Fiber) {
const dom =
fiber.type == 'TEXT'
? document.createTextNode(fiber.textContent ?? '')
: document.createElement(fiber.type as keyof HTMLElementTagNameMap);
updateDom(dom, undefined, fiber.props);
return dom;
}
const isNew =
(prev: Fiber['props'] = { children: [] }, next: Fiber['props'] = { children: [] }) =>
(key: string) =>
prev[key] !== next[key];
const isGone =
(_, next: Fiber['props'] = { children: [] }) =>
(key: string) =>
!(key in next);
export function updateDom(
dom: HTMLElement | Text,
prevProps: Fiber['props'] = { children: [] },
nextProps: Fiber['props'] = { children: [] },
) {
// 移除所有旧的事件监听器
Object.keys(prevProps)
.filter(isEvent)
.filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key))
.forEach((name) => {
const eventType = name.toLowerCase().substring(2);
dom.removeEventListener(eventType, prevProps[name] as EventListenerOrEventListenerObject);
});
// 添加事件监听器
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach((name) => {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, nextProps[name] as EventListenerOrEventListenerObject);
});
// 移除所有旧的属性
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach((name) => {
dom[name] = '';
});
// 设置所有属性
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach((name) => {
dom[name] = nextProps[name];
});
}
/**
* 协调 fiber 的新老直接子节点
* @param fiber fiber 节点
* @param children fiber 节点的直接子节点
*/
function reconcileChildren(fiber: Fiber, children: Fiber[]) {
let preChildFiber: Fiber | null = null;
let childIndex = 0;
const { length } = children;
let oldChild = fiber?.alternate?.child;
while (childIndex < length || oldChild != null) {
let newFiber: Fiber | null = null;
const child = children[childIndex];
/**
* old child 和 现在的 child 是相同类型
*/
const sameType = oldChild && child && child.type === oldChild.type;
/**
* child 是原始文本节点
*/
const isTextContent = !(child instanceof Object);
if (sameType) {
// update
newFiber = {
type: oldChild!.type,
props: child.props,
dom: oldChild!.dom,
parent: fiber,
alternate: oldChild,
textContent: isTextContent ? child : '',
tag: 'UPDATE',
};
}
if (child && !sameType) {
// add
newFiber = {
type: isTextContent ? 'TEXT' : child.type,
props: isTextContent ? { children: [] } : child.props,
dom: null,
parent: fiber,
alternate: null,
textContent: isTextContent ? child : '',
tag: 'ADD',
};
}
if (oldChild && !sameType) {
// delete
oldChild.tag = 'DELETE';
miniReact.deletedFibers.push(oldChild);
}
if (childIndex === 0) fiber.child = newFiber!;
else preChildFiber!.sibling = newFiber!;
preChildFiber = newFiber;
oldChild = oldChild?.sibling;
childIndex++;
}
}
/**
* 状态钩子
* @param initialState 初始状态
*/
export const useState: UseState = <State>(initialState: State) => {
const oldHook: UseStateHook<State> | undefined =
miniReact.workInProgressFiber?.alternate?.useStateHooks?.[miniReact.workInProgressFiber!.useStateHookIndex!];
const hook: UseStateHook<State> = {
state: oldHook ? oldHook.state : initialState,
actions: [],
};
oldHook?.actions?.forEach((action) => {
hook.state = action instanceof Function ? action(hook.state) : action;
});
const setState = (action: (previousState: State) => State) => {
hook.actions.push(action);
miniReact.workInProgressRoot = {
type: 'div',
dom: miniReact.lastCommittedRoot?.dom,
props: miniReact.lastCommittedRoot?.props,
alternate: miniReact.lastCommittedRoot,
};
miniReact.deletedFibers = [];
miniReact.nextUnitOfWork = miniReact.workInProgressRoot;
};
miniReact.workInProgressFiber?.useStateHooks?.push?.(hook);
miniReact.workInProgressFiber!.useStateHookIndex!++;
return [hook.state, setState];
};