什么是fiber
Fiber
通过链表的形式来记录节点之间的关系,可以被理解为划分一个个更小的执行单元,它是把一个大任务拆分为了很多个小块任务,一个小块任务的执行必须是一次完成的,不能出现暂停,但是一个小块任务执行完后可以移交控制权给浏览器去响应用户,从而不用像之前一样要等那个大任务一直执行完成再去响应用户。
代码执行流程
- 项目运行创建 “浏览器空闲加载函数” 用于实时监听 执行单元 (fiber)
- 获取页面dom容器(root),将jsx代码生成新的fiber
- 检测存在新的fiber,执行当前fiber,并返回下一个fiber, 在该fiber上获取旧的fiber,进行节点判断,分更新、新增、删除,记录删除的节点
- 对标识进行对应的处理
/*重写 createElement */
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === "object" ? child : createTextElement(child))
}
}
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: []
}
}
}
/*渲染与提交*/
//事件监听
const isEvent = key => key.startsWith("on")
// 是否有子集
const isProperty = key => key !== "children" && !isEvent(key)
// 验证 属性值
const isNew = (prev, next) => key => prev[key] !== next[key]
//是否存在
const isGone = (prev, next) => key => !(key in next)
/**
*
* @param dom 真实dom
* @param prevProps 旧的
* @param nextProps 新的
*/
function updateDom(dom, prevProps, nextProps) {
console.log("dom", dom, prevProps, nextProps)
//删除旧的或更改的事件侦听器
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])
})
//删除旧属性
Object.keys(prevProps)
.filter(isProperty)//获取所以有子集的节点
.filter(isGone(prevProps, nextProps))//获取属性值一致的的节点
.forEach(name => {
dom[name] = ""
})
//设置新属性或更改的属性
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach(name => {1
dom[name] = nextProps[name]
})
//添加事件侦听器
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach(name => {
const eventType = name.toLowerCase().substring(2)
dom.addEventListener(eventType, nextProps[name])
})
}
// Fiber 树提交给 DOM
function commitRoot() {
deletions.forEach(commitWork)
commitWork(wipRoot.child)
currentRoot = wipRoot
wipRoot = null
}
function commitWork(fiber) {
console.log("fiber", fiber)
if (!fiber) return
//要找到 DOM 节点的父节点,我们需要沿着纤程树向上查找,直到找到具有 DOM 节点的纤程
let domParentFiber = fiber.parent
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.parent
}
const domParent = domParentFiber.dom
//fiber递归转换为dom
// const domParent = fiber.parent.dom
if (
fiber.effectTag === "PLACEMENT" &&
fiber.dom != null
) {
//将 PLACEMENT 标识的添加到父DOM上
domParent.appendChild(fiber.dom)
} else if (
fiber.effectTag === "UPDATE" &&
fiber.dom != null
) {
//更改 UPDATE 标识的props
updateDom(
fiber.dom,
fiber.alternate.props,
fiber.props
)
} else if (fiber.effectTag === "DELETION") {
// 相反!删除 DELETION 标识的节点
commitDeletion(fiber, domParent)
}
commitWork(fiber.child)
commitWork(fiber.sibling)
}
/*重写 render */
function createDom(fiber) {
//元素类型创建 DOM 节点。如果元素类型是TEXT_ELEMENT我们创建一个文本节点而不是常规节点
// const dom = document.createElement(element.type)
const dom = fiber.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(fiber.type)
//分配元素的属性
// const isProperty = key => key !== "children"
// Object.keys(element.props).filter(isProperty).forEach(name => {
// dom[name] = element.props[name]
// })
updateDom(dom, {}, fiber.props)
return dom
}
//并且当移除一个节点时,我们还需要继续,直到我们找到一个带有 DOM 节点的子节点
function commitDeletion(fiber, domParent) {
if (fiber.dom) {
domParent.removeChild(fiber.dom)
} else {
commitDeletion(fiber.child, domParent)
}
}
function render(element, container) {
/*当前进行中的 fiber */
wipRoot = {
dom: container,
props: {
children: [element]
},
alternate: currentRoot // alternate 旧的 fiber
}
//跟踪我们想要删除的节点 初始化
deletions = []
console.log("wipRoot", wipRoot)
//该 fiber 也将是下一个执行单位(工作区)
nextUnitOfWork = wipRoot
}
/*并发 时间切片*/
/** 下一个工作区*/
let nextUnitOfWork = null
/** 进行中的根 */
let wipRoot = null
/** 保存提交给 DOM 的最后一个纤程树*/
let currentRoot = null
/** 跟踪我们想要删除的节点*/
let deletions = null
// 工作循环
function workLoop(deadline) {
let shouldYield = false
//当前工作区不为空时
while (nextUnitOfWork && !shouldYield) {
//执行工作区 (fiber)
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1
console.log("shouldYield",shouldYield)
}
//没有下一个工作单元,且存在根时
if (!nextUnitOfWork && wipRoot) {
//Fiber 树提交给 DOM
commitRoot()
}
requestIdleCallback(workLoop)
}
//浏览器空闲时期被调用 执行最新工作区
requestIdleCallback(workLoop)
//执行工作单元
function performUnitOfWork(fiber) {
const isFunctionComponent = fiber.type instanceof Function
//将新旧fiber的子集进行对比,划分出标识
if (isFunctionComponent) {
//fiber 是否为函数 hooks
updateFunctionComponent(fiber)
} else {
//fiber 非函数类型时
updateHostComponent(fiber)
}
//寻找下一个工作单元
if (fiber.child) {//孩子
console.log("fiber.child",fiber)
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
if (nextFiber.sibling) {//兄弟
return nextFiber.sibling
}
nextFiber = nextFiber.parent //叔叔
}
}
let wipFiber = null
let hookIndex = null
/** 将新旧fiber的子集进行对比,划分出标识*/
function updateFunctionComponent(fiber) {
wipFiber = fiber
hookIndex = 0
wipFiber.hooks = []
const children = [fiber.type(fiber.props)]
reconcileChildren(fiber, children)
}
function useState(initial) {
const oldHook = wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex]
const hook = {
state: oldHook ? oldHook.state : initial,
query: []
}
console.log("hooks", hook)
const action = oldHook ? oldHook.query : []
action.forEach(action => {
hook.state = action(hook.state)
})
const setState = action => {
hook.query.push(action)
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot
}
//将一个新的正在进行的工作根设置为下一个工作单元,这样工作循环就可以开始一个新的渲染阶段
nextUnitOfWork = wipRoot
deletions = []
}
wipFiber.hooks.push(hook)
hookIndex++
return [hook.state, setState]
}
function updateHostComponent(fiber) {
//当fiber为空是 创建一个新节点并将其附加到 DOM
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
//每次我们处理一个元素时,我们都会向 DOM 添加一个新节点 浏览器可能会在我们完成渲染整个树之前中断我们的工作。在这种情况下,用户将看到一个不完整的 UI。我们不希望这样。
// if(fiber.parent){
// fiber.parent.dom.appendChild(fiber.dom)
// }
//然后我们为每个孩子创造一个新的纤维
reconcileChildren(fiber, fiber.props.children)
}
//这element是我们想要渲染到 DOM 的东西,oldFiber也是我们上次渲染的东西
function reconcileChildren(wipFiber, children) {
let index = 0
//上次渲染的 fiber 的子集
let oldFiber = wipFiber.alternate && wipFiber.alternate.child
let prevSibling = null
while (index < children.length || oldFiber != null) {
//获取当前 fiber 的子集
const element = children[index]
let newFiber = null
const sameType = oldFiber && element && element.type == oldFiber.type
//当旧的 Fiber 和元素的类型相同时,我们创建一个新的 Fiber,保留旧 Fiber 的 DOM 节点和元素的 props。
if (sameType) {
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,//记录当前fiber的父级
alternate: oldFiber,
effectTag: "UPDATE",//fiber 标记 更新
}
}
if (element && !sameType) {
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: wipFiber,
alternate: null,
effectTag: "PLACEMENT",//fiber 标记 新的
}
}
//删除节点
if (oldFiber && !sameType) {
oldFiber.effectTag = "DELETION"//fiber 标记 删除
deletions.push(oldFiber)
}
if (oldFiber) {
oldFiber = oldFiber.sibling
}
// const newFiber = {
// type: element.type,
// props: element.props,
// parent: fiber,
// dom: null
// }
if (index === 0) {
wipFiber.child = newFiber
} else {
prevSibling.sibling = newFiber
}
prevSibling = newFiber
index++
}
}
/*end 并发 时间切片*/
const Didact = {
createElement,
render,
useState,
}
/** @jsx Didact.createElement */
function Counter() {
const [state, setState] = Didact.useState(1)
return (
<h1 onClick={() => setState(c => c + 1)}>
Count: {state}
</h1>
)
}
// const element = Didact.createElement(
// "div",
// {id: "foo"},
// Didact.createElement("a", null, "bar"),
// Didact.createElement("b"),
// )
/*JSX转换*/
const element = <Counter/>
const container = document.getElementById("root")
Didact.render(element, container
注:主要用过个人记录学习