📝
React Fiberのアルゴリズムを実装してみた
いまさらながらReact Fiberのアルゴリズムを実装してみました。
300行くらいですが、プログラムの名前や責務など、本物のReactをできるかぎり忠実に再現するようにしています。実装は以下にポイントを絞りました。
- Fiberのデータ構造とトラバースアルゴリズム
- 中断可能な「Unit of work」
- RenderフェーズとCommitフェーズ
アーキテクチャは以下のような感じ
コード上にReactのソースコードへのリンクもコメントしているので、見比べてみるとより理解が深まると思います(例えば、今回はrequestIdleCallback
で擬似的にスケジューリングを実現していますが、Reactでは独自にSchedulerをつくっています)。
Fiberのアーキテクチャに関しては既にたくさんの情報があるため、この記事ではいくつか登場人物を紹介するまでとします。
performUnitOfWork
Fiberをトラバースして次のUnit of workを決める
// https://github.com/okmttdhr/react/blob/master/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L3206
function beginWork(fiber) {
const isFunctionComponent =
fiber.type instanceof Function
if (isFunctionComponent) {
updateFunctionComponent(fiber)
} else {
updateHostComponent(fiber)
}
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling
}
nextFiber = nextFiber.parent
}
}
// https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberWorkLoop.new.js#L1574
function performUnitOfWork(fiber) {
nextUnitOfWork = beginWork(fiber)
if (!nextUnitOfWork) {
commitRoot()
completeUnitOfWork()
}
}
workLoop
仕事がなくなるまでループする
ブラウザが忙しくなったらループを中断して終わってから戻ってくる
// https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1567
function workLoop(deadline) {
let shouldYield = false
while (workInProgressRoot && !shouldYield) {
performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1
}
requestIdleCallback(workLoop)
}
reconcileChildren
差分検知とfiberの更新
// https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L255
function reconcileChildren(workInProgressFiber, elements) {
let index = 0
let oldFiber =
workInProgressFiber.alternate && workInProgressFiber.alternate.child
let prevSibling = null
while (
index < elements.length ||
oldFiber != null
) {
const element = elements[index]
let newFiber = null
const sameType =
oldFiber &&
element &&
element.type == oldFiber.type
if (sameType) {
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: workInProgressFiber,
alternate: oldFiber,
flag: UPDATE,
}
}
if (element && !sameType) {
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: workInProgressFiber,
alternate: null,
flag: PLACEMENT,
}
}
if (oldFiber && !sameType) {
oldFiber.flag = DELETION
deletions.push(oldFiber)
}
if (oldFiber) {
oldFiber = oldFiber.sibling
}
if (index === 0) {
workInProgressFiber.child = newFiber
} else if (element) {
prevSibling.sibling = newFiber
}
prevSibling = newFiber
index++
}
}
commitWork
DOMの反映(Commitフェーズ)
// https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberCommitWork.new.js#L1814
function commitWork(fiber) {
if (!fiber) {
return
}
let parentFiber = fiber.parent
while (!parentFiber.dom) {
parentFiber = parentFiber.parent
}
const parentDom = parentFiber.dom
if (
fiber.flag === PLACEMENT &&
fiber.dom != null
) {
commitPlacement(fiber, parentDom)
} else if (
fiber.flag === UPDATE &&
fiber.dom != null
) {
commitUpdate(
fiber.dom,
fiber.alternate.props,
fiber.props
)
} else if (fiber.flag === DELETION) {
commitDeletion(fiber, parentDom)
}
commitWork(fiber.child)
commitWork(fiber.sibling)
}
// https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberWorkLoop.new.js#L1693
function commitRoot() {
deletions.forEach(commitWork)
commitWork(workInProgressRoot.child)
}
突っ込みやPRなど歓迎です。
Discussion