fe-fwk-ts-react
A notes app built with a TypeScript port of the framework from the book
Build a Frontend Web Framework (From Scratch), with React’s names. Move the slider through seven edits of a notes list and see two numbers for each: how
many components ran render(), and how many times the page’s DOM really
changed.
Legend:
render() ran: the component computed its next tree
DOM write: the browser changed the page
table: changed in this step, old value struck through
render() 0 · DOM writes 0
render() returns the component’s next tree of plain objects. A
console.log in it prints every time, but the page changes only where the new
tree differs from the old one. App renders on every step, and most of what it returns is already on the page.
Editing a note, adding to the end, removing the last: turn keys off and steps 2 to 4 give the same page and the same DOM writes. Without keys, a child is matched by its position, and these edits keep positions.
Add to the top without keys and the page still reads right: every position gets new props, one
<li> is added at the end. But the table shows that state and the typed text didn’t move.
The framework is right by its own rules: same position, same component.
One difference from React: here a component whose props didn’t change skips render(),
which is what React.memo does. Plain React calls every child of a component that renders.
The DOM writes are the same.
Requires Node.js 22 or newer. The runtime isn’t published to npm: it’s a learning project, so you clone it.
git clone https://github.com/slimcandy/fe-fwk-ts-react
cd fe-fwk-ts-react
npm install
npm test # the book's tests and the React comparison
npm run dev # this page, at http://localhost:5173
import {
createApp,
createComponent,
createElement,
} from "fe-fwk-ts-react";
const Counter = createComponent({
state() {
return { count: 0 };
},
render() {
const { count } = this.state;
return createElement(
"button",
{ on: { click: () => this.setState({ count: count + 1 }) } },
[`Clicked ${count} times`]
);
},
});
createApp(Counter).mount(document.body);