簡易 React 自作 を試してみて
React 自作の記事があったので、試してみました。
ただ、やってみて分かりづらい部分が多々あったので、そのまとめをします。
reconcileChildren の中の prevSibling.sibling = newFiber ってなんだ?
if (index === 0) {
wipFiber.child = newFiber
} else {
prevSibling.sibling = newFiber
}
prevSibling = newFiber
これは github に答えがありました。
1:まず、初回のループ(index === 0)の時は、wipFiber.child に 初回の newFiber を追加して、prevSibling に 初回の newFiber を代入します。
2:次のループ(index === 1)の時は、prevSibling に入っている 初回の newFiber の siblingプロパティーに 2回目の newFiber が代入されます。そして、prevSibling に 2回目の newFiber が代入されます。
3:その次以降のループも同じようになる。
言ってしまえば、簡単ですね。
これで、各 parent と 最初の各children のみが連結され、sibling は 最初の child から数珠繋ぎになっている、二重連鎖木 ができますね。
alternate って、最初の render の時にしか定義されていないような・・・?
alternate と言えば、reconcileChildren で古いFiber と新しいFiberを比較するためのプロパティですね。
このalternate、コードを読むと代入されている部分が3箇所のみで、使われているのが2箇所のみになっています(「Step6:差分検出」まででは)。
代入されている部分
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element]
},
alternate: currentRoot
}
deletions = []
nextUnitOfWork = wipRoot
}
if (sameType) {
// TODO Update node
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE"
}
}
if (element && !sameType) {
// TODO Add node
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: wipFiber,
alternate: null,
effectTag: "PLACEMENT"
}
}
使われている部分
let oldFiber = wipFiber.alternate && wipFiber.alternate.child
updateDom(fiber.dom, fiber.alternate.props, fiber.props)
ここで、一度 "PLACEMENT" が終わった後で、再レンダリングし "UPDATE" が行われた場合を考える。
// UseCase2
const onInputWord = (event) => {
rerender(event.target.value)
}
const container = document.getElementById("root")
const rerender = value => {
const element = Didact.createElement(
"div",
{ id: "foo"},
Didact.createElement("a", null, Didact.createElement("h2", null, "bar")),
Didact.createElement("br"),
Didact.createElement("b", null,
Didact.createElement("h4", null,
Didact.createElement("i", null, `Hello ${value}`)
)
),
Didact.createElement("br"),
Didact.createElement("input", {onInput: onInputWord, defaultValue: value})
)
Didact.render(element, container)
}
rerender("World")
この使われている部分の wipFiber.alternate && wipFiber.alternate.child が、初回 render時 の alternate が代入されている時に入るのは分かるが、次の wipFiber.child が nextUnitOfWork に入る
if (fiber.child) {
return fiber.child
}
では、どうなっているか分かりづらい。
ただ、ここの順番は、
const elements = fiber.props.children
reconcileChildren(fiber, elements)
if (fiber.child) {
return fiber.child
}
レコンサイル (reconcileChildren) して、fiber に child オプションなどを付与してから 次の performUnitOfWork に移っているので、次の performUnitOfWork の中の reconcileChildren では alternate が null になることはない。
初回の render 時のレコンサイルから説明すると、render して alternate が付与された nextUnitOfWork = wipRoot は、まず nextUnitOfWork が null でなくなったので、performUnitOfWork で reconcileChildren します。
reconcileChildren では、wipRoot が wipFiber に入っているので、oldFiber には null は入りません。
ここで、sameType の判定が入りますが、ここでは oldFiber に入っている alternate.child と element[0] は同じなので(wipRootの子要素のtypeが変わっていない限り。なお今回のインプットフォームではwipRootの子要素のtypeは変わりません)、alternate に oldFiber が入った newFiber が作成され、wipFiber の child に newFiber が代入されます。
二回目の index の while では、wipRootの子要素のtypeが変わっていないので、上とほぼ同じですが、prevSibling の sibling に newFiber が代入されます。
この reconcileChildren が終了すると、child が見つかる場合は performUnitOfWork (fiber.child) が実行されますが、ここの fiber.child にも 二度目の rerender 時には alternate が付与されています。
Sibling の場合にも、二度目の rerender 時には alternate が付与されます。
reconcileChildren の後で if (fiber.child) している順番通りです。
まとめると、
const elements = fiber.props.children
reconcileChildren(fiber, elements)
if (fiber.child) {
return fiber.child
}
// 省略
return nextFiber.sibling
の順番通り、reconcileChildren で child と sibling に alternate が入った newFiber を代入してから、return される child と 各sibling で performUnitOfWork (nextUnitOfWork) をします。