解构前端框架之Fiber
js
const {div, button, fragment, h, createRoot, useState, useEffect} = window.React;
const App = (state) => {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(state.count);
}, [state.count]);
return fragment([
button(
[`Depth: ${state.depth}, Count: ${count}`],
{
onClick: () => setCount(count + 1),
},
),
h(App, {
depth: state.depth + 1,
count
})
].filter(() => state.depth < 1e3))
};
createRoot(document.body).render(h(App, {depth: 0, count: 0}));

jsx
const App = (state) => {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(state.count);
}, [state.count]);
return <>
<button onClick={() => setCount(count + 1)}>Depth: {state.depth}, Count: {count}</button>
{state.depth < 1e3 && (
<App depth={state.depth + 1} count={count} />
)}
</>;
}
createRoot(document.body).render(<App depth={0} count={0} />);

ts
function evalSeq(nodes: VNode[]) {
return flatten(nodes.map(evalVNode)) as Node[]; // flattern
}
export function evalButton(node: VNodeButton) {
const btn = document.createElement('button');
if (node.attr?.style) {
bindStyle(btn, node.attr.style);
}
if (node.attr?.onClick) {
btn.addEventListener('click', node.attr.onClick);
}
btn.append(...evalSeq(node.children));
node.output = [btn];
return btn;
}
ts
function evalSeq(nodes: VNode[], callback: (output:Node[]) => void) {
if (nodes.length === 0) {
callback([]);
return;
}
evalVNode(nodes[0], (firstOutput) => {
evalSeq(nodes.slice(1), (restOutput) => {
callback([...firstOutput, ...restOutput]);
});
});
}
export function evalButton(node: VNodeButton, callback:(output: Node[]) => void) {
const btn = document.createElement('button');
if (node.attr?.style) {
bindStyle(btn, node.attr.style);
}
if (node.attr?.onClick) {
btn.addEventListener('click', node.attr.onClick);
}
evalSeq(node.children, (output) => {
btn.append(...output);
node.output = [btn];
callback(node.output);
});
}
ts
export type Task = {
priority: number;
job: () => void
};
export class TaskQueue {
tasks: PriorHeap<Task>;
readonly frameLimit: number;
private channel:MessageChannel;
private running = false;
constructor(frameLimit = 1000 / 60) {
this.tasks = new PriorHeap<Task>([], (a, b) => a.priority < b.priority);
this.frameLimit = frameLimit;
this.channel = new MessageChannel();
this.channel.port1.onmessage = () => this.flushTask();
}
schedule(task: Task['job']):void;
schedule(task: Task):void;
schedule(task: any) {
if (typeof task === 'function') {
this.tasks.push({ job: task, priority: /* lowest */ Number.MAX_SAFE_INTEGER });
} else {
this.tasks.push(task);
}
this.flushTask();
}
protected flushTask() {
if (this.running) return;
this.running = true;
const start = Date.now();
while (true) {
const top = this.tasks.pop();
if (!top) {
this.running = false;
break;
}
top.job();
if (this.tasks.length === 0) {
this.running = false;
break;
}
const elapsed = Date.now() - start;
if (elapsed >= this.frameLimit) {
// shedule next loop
this.channel.port2.postMessage('');
this.running = false;
break;
}
}
}
}
ts
function evalSeq(nodes: VNode[], callback: (output:Node[]) => void) {
if (nodes.length === 0) {
callback([]);
return;
}
evalVNode(nodes[0], (firstOutput) => {
evalSeq(nodes.slice(1), (restOutput) => {
queue.schedule(() => evalSeq(nodes.slice(1), (restOutput) => {
callback([...firstOutput, ...restOutput]);
}));
});
}
