The last review

In the last video we explained what VDOM is, why we use VDOM, and how we can implement a VDOM. So if we look at the blueprint again, what we’re going to implement today is the left half of the diagram.

package.json

{" name ":" vdom ", "version" : "1.0.0", "description" : ""," scripts ": {" compile" : "babel index.js --out-file compiled.js" }, "author": "", "license": "", "devDependencies": { "babel-cli": "^ 6.23.0", "Babel - plugin - transform - react - JSX" : "^ 6.23.0"}}

There are two main points here:

  1. devDependenciesWe rely on babel-cli and babel-plugin-transform-react-jsx. The former provides Babel’s command-line functionality, while the latter helps us convert JSX to JS.
  2. scriptsWhere we specify a command:complileEvery time we type on the command line in the current directorynpm run compile“, Babal will turn ourindex.jsConvert and create a new onecompile.jsFile.

When you are done, type NPM install on the command line.

.babelrc

{
  "plugins": [
    ["transform-react-jsx", {
      "pragma": "h"  // default pragma is React.createElement
    }]
  ]
}

In Babel’s configuration file, we specified the transform-react-jsx plugin to set the transformed function name to h. The default function name is React. CreateElement. We don’t rely on React, so it’s obviously more appropriate to change our own name. It doesn’t matter if you don’t know what h is, but you’ll see it when you look at the code.

index.html

<! DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>VDOM</title> <style> body { margin: 0; font-size: 24; font-family: sans-serif } .list { text-decoration: none } .list .main { color: red } </style> </head> <body> <script src="compiled.js"></script> <div id="app"></div> <script> var app = document.getElementById('app') render(app) </script> </body> </html>

This HTML is intuitive, like React, where we have a root node ID called app. And then the DOM that our render function ends up generating is going to be inserted into the root node of our app. Note that the compile.js file we refer to is automatically generated by Babel from the index.js file that we will write later.

index.js

First, we write the “template” in JSX:

function view() {
  return <ul id="filmList" className="list">
    <li className="main">Detective Chinatown Vol 2</li>
    <li>Ferdinand</li>
    <li>Paddington 2</li>
  </ul>
}

Next, we need to compile JSX into JS, which is Hyperscript. So let’s start with Babel and see what JSX looks like when it turns into JS. Go to the command line and type NPM run compile.

function view() {
  return h(
    "ul",
    { id: "filmList", className: "list" },
    h(
      "li",
      { className: "main" },
      "Detective Chinatown Vol 2"
    ),
    h(
      "li",
      null,
      "Ferdinand"
    ),
    h(
      "li",
      null,
      "Paddington 2"
    )
  );
}

The first argument is the node type (ul,li), the second argument is the node property, and then the children of the node. If the child is another node, the h function will be called again.

Now that we know what Babel will compile into our JSX, we can proceed with writing h in index.js.

function flatten(arr) { return [].concat(... arr) } function h(type, props, ... children) { return { type, props: props || {}, children: flatten(children) } }

The main job of our h function is to return the HyperScript object that we really need, with only three arguments, the first being the node type, the second being the properties object, and the third being an array of children.

The rest, spread parameter of ES6 is mainly used here. You can read my introduction to ES6 article in 30 minutes to master the core content of ES6/ES2015 (I). In short, REST is the above… Children, which puts the extra arguments to the function into an array, so children becomes an array. Spread is the inverse of rest, i.e., above… Arr, which converts an array to a comma-separated sequence of arguments.

Flatten (children) is the operation because the elements in the children array might also be an array, which would make it a two-dimensional array, so we need to flatten the array into a one-dimensional array. [].concat(… Apply ([]. Concat. apply([], arr).

We can now take a look at what the final object returned by h will look like.

function render() {
  console.log(view())
}

We print out the result of executing view() in the render function, then NPM run compile, open our index.html with the browser, see the console output results.

Yes, it’s perfect! This object is our VDOM!

So now we can render the actual DOM based on the VDOM. First rewrite the render function:

function render(el) {
  el.appendChild(createElement(view(0)))
}

The createElement function generates the DOM and then inserts it into the root node app that we wrote in index.html. Note that the render function is called in index.html.

function createElement(node) {
  if (typeof(node) === 'string') {
    return document.createTextNode(node)
  }

  let { type, props, children } = node
  const el = document.createElement(type)
  setProps(el, props)
  children.map(createElement)
    .forEach(el.appendChild.bind(el))

  return el
}

function setProp(target, name, value) {
  if (name === 'className') {
    return target.setAttribute('class', value)
  }

  target.setAttribute(name, value)
}

function setProps(target, props) {
  Object.keys(props).forEach(key => {
    setProp(target, key, props[key])
  })
}

Let’s take a closer look at the createElement function. If we say that Node, or VDOM, is of type text, we simply return a created text node. Otherwise, we need to retrieve the node type, properties, and children, create the target node according to the type, and then call setProps to set the properties of the target node in turn. Finally, traversal the children, recursively call createElement method, and insert the returned children into the newly created target node. The destination node is finally returned.

One more thing to note is that the class in JSX is written as className, so I need to do something special.

You’re done. After complie, open your browser to index.html and see the results.

Today we successfully completed the left half of the blueprint, transforming JSX into Hyperscript, then into VDOM, and finally generating DOM according to VDOM, rendering to the page. Tomorrow, we’re up to the challenge of dealing with the renderings caused by data changes, how we’ll DIFF the old and new VDOM, generate patches, and modify the DOM.