vdom:
16v 前: vdome -> 递归 diff render;
递归渲染时做 diff 来确定增删改以及创建 dom
- 通过children关联父子节点
- 递归diff 不可中断
- 影响性能 影响用户体验
react的setState会渲染整个页面,当应用vdom过于庞大,层级过深,计算量就会增大,在进行一些用户操作如输入框输入时,会导致diff和渲染事件过长 导致页面卡顿影响用户体验
fiber框架:
16v 后:vdome -> filber -> render(commit)
fiber链表结构,可以打断,这样就可以通过requestIdleCallback
来空闲调度reconciler
,这样不断的循环,直到处理完所有的vdom转fiber的reconciler
,就开始commit,也就是更新到 dom。
- 通过child 关联第一个子节点,通过sibling串联 所有节点都可以return父节点
- 空闲时间执行 循环转换
- 可中断 优先执行 用户交互 页面滚动 尺寸变更
- 设置更新优先级
- 生命周期、用户操作
- 交互时间
- 数据请求
scheduler(调度器)
通过scheduler去循环调用chrome的apirequestIdleCallback
查询当前浏览器是否空闲,空闲的话执行reconciler(将vdome转fiber);
reconciler(调节器)
- vdom转fiber,并提前创建对应的dome节点、做diff 将effectTag放到effectList中
- 创建根fiber节点, 并且下一个处理的fiber指向这个节点,在下次scheduler就会调度这个fiber节点,开始reconciler
- 当前的fiber节点,然后按照顺序 处理child、sibling 处理完成 return fiber节点, 不断调度;
commit(render)
- 把fiber链表,添加到dom中,通过effectList对dom进行增删改
- reconciler阶段对应fiber节点已创建,所以render会比之前vdom渲染会更快
requestIdleCallback(callback[, options])
- callback: 空闲时间需要执行的任务;回接收一个IdleDeadline对象{ didTimeout: boolean(任务是否超时), timeRemaining(): 当前帧剩余时间(留给任务的空闲时间) }
- options: timeout; 表示超过这个时间后,如果任务还没执行,将强制执行 不必等待空闲。
// 简易版 react
// 记录当前fiber节点、根fiber节点
let nextFiberReconcileWork = null;
let wipRoot = null;
function workLoop(deadline) {
let shouldYield = false;
while (nextFiberReconcileWork && !shouldYield) {
nextFiberReconcileWork = performNextWork(nextFiberReconcileWork); // performNextWork reconciler
shouldYield = deadline.timeRemaining() < 1; // 是否可以渲染
}
// 如果节点为空就全部commit
if (!nextFiberReconcileWork) {
commitRoot();
}
window.requestIdleCallback(workLoop);
}
window.requestIdleCallback(workLoop); // requestIdleCallback chrome api
// reconciler 每次执行
function performNextWork(fiber) {
reconciler(fiber);
// 如果节点有child返回fiber
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.return;
}
}
// reconcile
// 创建根节点 赋值给wipRoot 并把下一个处理的fiber节点指向它
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
},
};
nextFiberReconcileWork = wipRoot;
}
function reconciler(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
// fiber.props.children: vdom子节点 把之前的vdom转成child、sibling、return 串联的链表
reconcilerChildren(fiber, fiber.props.children);
}
// 循环处理每个vdom的elements, index为0 child串联,否则sibling串联. 创建出的节点 都要用return指向父节点
function reconcilerChildren(wipFiber, elements) {
let index = 0;
let prevSibling = null;
while (index < elements.length) {
const element = elements[index];
let newFiber = {
type: element.type,
name: element.name,
props: element.props,
dom: null,
return: wipFiber,
effectTag: "PLACEMENT",
};
if (index === 0) {
wipFiber.child = newFiber;
} else if (element) {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
}
// commit
function commitRoot() {
commitWork(wipRoot.child);
// 不在需要调度 设为空
wipRoot = null;
}
function commitWork(fiber) {
if (!fiber) {
return;
}
let domParentFiber = fiber.return;
while (!domParentFiber.data) {
domParentFiber = domParentFiber.return;
}
const domParent = domParentFiber.dom;
if (fiber.effectTag === "PLACEMENT" && fiber.dom !== null) {
domParent.appendChild(fiber.dom);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
//根据类型创建元素,然后设置属性; 属性分别处理 style、文本节点的 value、事件监听器:
function createDom(fiber) {
const dom =
fiber.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(fiber.type);
for (const prop in fiber.props) {
setAttribute(dom, prop, fiber.props[prop]);
}
return dom;
}
// 判断是否为事件监听器
function isEventListenerAttr(key, value) {
return typeof value === "function" && key.startsWith("on");
}
// 判断是否为style
function isStyleAttr(key, value) {
return key === 'style' && typeof value === 'object';
}
// 判断是否为value
function isPlainAttr(key, value) {
return typeof value !== 'object' && typeof value !== 'function';
}
function setAttribute(dome, key, value) {
if(key === 'children'){
return;
}
if(key === 'nodeValue'){
dom.textContent = value;
} else if(isEventListenerAttr(key, value)){
const eventType = key.slice(2).toLowerCase();
document.addEventListener(eventType, value);
} else if(isStyleAttr(key, value)){
Object.assign(dom.style, value);
} else if(isPlainAttr(key, value)){
dom.setAttribute(key, value);
}
}