forked from pomber/didact
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request pomber#8 from pomber/hooks
Rewrite Didact again
- Loading branch information
Showing
38 changed files
with
361 additions
and
9,261 deletions.
There are no files selected for viewing
File renamed without changes.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"semi": false, | ||
"printWidth": 52, | ||
"trailingComma": "es5" | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,319 @@ | ||
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: [], | ||
}, | ||
} | ||
} | ||
|
||
function createDom(fiber) { | ||
const dom = | ||
fiber.type == "TEXT_ELEMENT" | ||
? document.createTextNode("") | ||
: document.createElement(fiber.type) | ||
|
||
updateDom(dom, {}, fiber.props) | ||
|
||
return dom | ||
} | ||
|
||
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) | ||
function updateDom(dom, prevProps, nextProps) { | ||
//Remove old or changed event listeners | ||
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] | ||
) | ||
}) | ||
|
||
// Remove old properties | ||
Object.keys(prevProps) | ||
.filter(isProperty) | ||
.filter(isGone(prevProps, nextProps)) | ||
.forEach(name => { | ||
dom[name] = "" | ||
}) | ||
|
||
// Set new or changed properties | ||
Object.keys(nextProps) | ||
.filter(isProperty) | ||
.filter(isNew(prevProps, nextProps)) | ||
.forEach(name => { | ||
dom[name] = nextProps[name] | ||
}) | ||
|
||
// Add event listeners | ||
Object.keys(nextProps) | ||
.filter(isEvent) | ||
.filter(isNew(prevProps, nextProps)) | ||
.forEach(name => { | ||
const eventType = name | ||
.toLowerCase() | ||
.substring(2) | ||
dom.addEventListener( | ||
eventType, | ||
nextProps[name] | ||
) | ||
}) | ||
} | ||
|
||
function commitRoot() { | ||
deletions.forEach(commitWork) | ||
commitWork(wipRoot.child) | ||
currentRoot = wipRoot | ||
wipRoot = null | ||
} | ||
|
||
function commitWork(fiber) { | ||
if (!fiber) { | ||
return | ||
} | ||
|
||
let domParentFiber = fiber.parent | ||
while (!domParentFiber.dom) { | ||
domParentFiber = domParentFiber.parent | ||
} | ||
const domParent = domParentFiber.dom | ||
|
||
if ( | ||
fiber.effectTag === "PLACEMENT" && | ||
fiber.dom != null | ||
) { | ||
domParent.appendChild(fiber.dom) | ||
} else if ( | ||
fiber.effectTag === "UPDATE" && | ||
fiber.dom != null | ||
) { | ||
updateDom( | ||
fiber.dom, | ||
fiber.alternate.props, | ||
fiber.props | ||
) | ||
} else if (fiber.effectTag === "DELETION") { | ||
commitDeletion(fiber, domParent) | ||
} | ||
|
||
commitWork(fiber.child) | ||
commitWork(fiber.sibling) | ||
} | ||
|
||
function commitDeletion(fiber, domParent) { | ||
if (fiber.dom) { | ||
domParent.removeChild(fiber.dom) | ||
} else { | ||
commitDeletion(fiber.child, domParent) | ||
} | ||
} | ||
|
||
function render(element, container) { | ||
wipRoot = { | ||
dom: container, | ||
props: { | ||
children: [element], | ||
}, | ||
alternate: currentRoot, | ||
} | ||
deletions = [] | ||
nextUnitOfWork = wipRoot | ||
} | ||
|
||
let nextUnitOfWork = null | ||
let currentRoot = null | ||
let wipRoot = null | ||
let deletions = null | ||
|
||
function workLoop(deadline) { | ||
let shouldYield = false | ||
while (nextUnitOfWork && !shouldYield) { | ||
nextUnitOfWork = performUnitOfWork( | ||
nextUnitOfWork | ||
) | ||
shouldYield = deadline.timeRemaining() < 1 | ||
} | ||
|
||
if (!nextUnitOfWork && wipRoot) { | ||
commitRoot() | ||
} | ||
|
||
requestIdleCallback(workLoop) | ||
} | ||
|
||
requestIdleCallback(workLoop) | ||
|
||
function performUnitOfWork(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 | ||
} | ||
} | ||
|
||
let wipFiber = null | ||
let hookIndex = null | ||
|
||
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, | ||
queue: [], | ||
} | ||
|
||
const actions = oldHook ? oldHook.queue : [] | ||
actions.forEach(action => { | ||
hook.state = action(hook.state) | ||
}) | ||
|
||
const setState = action => { | ||
hook.queue.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) { | ||
if (!fiber.dom) { | ||
fiber.dom = createDom(fiber) | ||
} | ||
reconcileChildren(fiber, fiber.props.children) | ||
} | ||
|
||
function reconcileChildren(wipFiber, elements) { | ||
let index = 0 | ||
let oldFiber = | ||
wipFiber.alternate && wipFiber.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: wipFiber, | ||
alternate: oldFiber, | ||
effectTag: "UPDATE", | ||
} | ||
} | ||
if (element && !sameType) { | ||
newFiber = { | ||
type: element.type, | ||
props: element.props, | ||
dom: null, | ||
parent: wipFiber, | ||
alternate: null, | ||
effectTag: "PLACEMENT", | ||
} | ||
} | ||
if (oldFiber && !sameType) { | ||
oldFiber.effectTag = "DELETION" | ||
deletions.push(oldFiber) | ||
} | ||
|
||
if (oldFiber) { | ||
oldFiber = oldFiber.sibling | ||
} | ||
|
||
if (index === 0) { | ||
wipFiber.child = newFiber | ||
} else if (element) { | ||
prevSibling.sibling = newFiber | ||
} | ||
|
||
prevSibling = newFiber | ||
index++ | ||
} | ||
} | ||
|
||
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 = <Counter /> | ||
const container = document.getElementById("root") | ||
Didact.render(element, container) |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.