React in 33 lines

598

React

  • you pass in a function that takes state and returns a virtual DOM (just a tree of plain ol’ js objects)
  • it renders that virtual DOM as a real DOM in the browser
  • if you change the state, it runs the function again, this returns a new virtual DOM
  • it efficiently updates the real DOM so that it matches the new virtual DOM

It also does a load of other crap as well, but we’re going to ignore that.

In this post, I’m going to make the smallest React-like thing that can do the above. It’s very mithril influenced.

Here is a sample calendar picker application that uses the library.

Lots of the code looks pretty code-golfy – I promise I don’t do stuff like this at work, neither should you 🙂

Noughts and crosses

We’re going to make this noughts and crosses game:

Now let’s look at the code to this, you can also just view the page source if you want.

let currentPlayer = 'o'
let winner = null
const g = [['', '', ''], ['', '', ''], ['', '', '']]  // grid

const move = (value, i, j)=>{
    // ... game logic goes here
    renderNoughts()
}

const Cell = (value, i, j)=>m('button.cell',
    {onclick: ()=>move(value, i, j)}, value
)
const Noughts = ()=>m('',
    winner
        ? m('marquee', `winner: ${winner}`)
        : m('h3', `current player: ${currentPlayer}`),
    m('table', g.map(
        (row, i)=>m('tr', row.map(
            (value, j)=>m('td', {class: value}, Cell(value, i, j)))))),
)

const renderNoughts = ()=>m.render(
    document.getElementById('noughts'),
    {children: [Noughts()]},
)
renderNoughts()

Cute, so what’s going on?

First we defined some state:

let currentPlayer = 'o'
let winner = null
const g = [['', '', ''], ['', '', ''], ['', '', '']]  // grid

These hold the state of our game, we will mutate them.

const move = (value, i, j){...}

This function makes a move in the game, it takes 'x' or 'o' along with 2 integer coordinates. It will mutate all the state variables to reflect the new state of the game. After that, it calls renderNoughts(), this is a call to rerender the game – but we’ll come back to that.

Next we define the functions that return virtual DOMs, Noughts and Cell.

The m(...) calls take:

  • a tag name (eg. 'tr'), with .-separated class names
  • (optionally) a {string: any} object containing all the attributes to attach to the DOM node
  • an arbitrarily nested list of children – these are other virtual DOM nodes or strings of text

And return virtual DOM elements, for example, calling Noughts() would return:

{
    tag: 'div',
    attrs: {},
    classes: [],
    children: [
        {
            tag: 'h3',
            attrs: {},
            classes: [],
            children: [
                'current player: x'
            ]
        },
        {
            tag: 'table',
            attrs: {},
            classes: [],
            children: [
                {
                    tag: 'tr',
                    attrs: {},
                    classes: [],
                    children: [
...

Next we make the function renderNoughts(), when you call it, it will call our Noughts function, and attempt to efficiently render the resulting virtual DOM onto document.getElementById('noughts')

How does m work?

Here’s the source with and without comments.

Read More