The React. The createElement method syntactic sugar

JSX is a syntactic extension of JavaScript that can be used for UI presentation:

const element = <h1>Hello, world!</h1>;
Copy the code

We typically use JSX in the render method of the component for layout and event binding:

class Home extends Component {
  render() {
    return (
      <div onClick={()= > console.log('hello')}>
        <h1>Hello, world!</h1>
        <Blog title="deepred" />
      </div>); }}Copy the code

One of the core mechanisms of React is the ability to create virtual DOM elements, which can be used to reduce manipulation of the real DOM to improve performance. JSX is the syntactic sugar for the virtual DOM

In our usual component writing, we usually write:

import React, { Component } from 'react';

class Demo extends Component {
  render() {
    return (
      <h1>Hello, world!</h1>)}}Copy the code

React is not used in the code, so why introduce this variable?

Because JSX is the syntactic sugar for the react. createElement method:

const element = <h1 id="container" className="home">Hello</h1>;

/ / equivalent to the
const element = React.createElement("h1", {
  id: "container".className: "home"
}, "Hello");
Copy the code

Babeljs. IO is recommended to see JSX compiled in action

React.createElement takes three arguments:

React.createElement(
  type, // DOM types, such as div, h1
  [props], // DOM attributes, such as id, class, event
  [...children] // Child node, string, or an object generated by react. createElement
)
Copy the code

JSX replaces the tedious react. createElement pure JS method with an HTMl-like syntax, and the @babel/preset- React plugin is the key step: Responsible for changing all JSX files to React. CreateElement when webpack builds:

class Home extends Component {
  render() {
    return (
      <div onClick={()= > console.log('hello')}>
        <h1>Hello, world!</h1>
        <Blog title="deepred" />
      </div>); }}Copy the code

The compiled:

class Home extends Component {
  render() {
    return React.createElement("div", {
      onClick: (a)= > console.log('hello')
    }, React.createElement("h1".null."Hello, world!"), React.createElement(Blog, {
      title: "deepred"})); }}Copy the code

In development, we rarely need to use the createElement method with JSX, but if we need to implement a component like this:

// Based on the type attribute passed in, Render the corresponding HTML element <Tag type="h1" id="hello" onClick={() => console.log('hello')}>this is a h1</Tag> <Tag type="p">this is a p</Tag>Copy the code

It is not possible to determine the corresponding tag according to the attribute of type, if else:

function Tag(props) {
  const{ type, ... other } = props;if (type === 'h1') {
    return <h1 {. other} >{props.children}</h1>
  }

  if (type === 'p') {
    return <p {. other} >{props.children}</p>}}Copy the code

In this case, we need to use the underlying API:

function Tag(props) {
  const{ type, ... other } = props;return React.createElement(type, other, props.children);
}
Copy the code

Implement a JSX renderer yourself

The virtual DOM is essentially a JS object:

const vnode = {
  tag: 'div'.attrs: {
    className: 'container'
  },
  children: [{tag: 'img'.attrs: {
          src: '1.png'
        },
        children: []}, {tag: 'h3'.attrs: {},
        children: ['hello']]}}Copy the code

Can tell @babel/preset-react to use h method name instead of JSX by adding /** @jsx h */ at the top of each file (the default is react.createElement)

/** @jsx h */

const element = <h1 id="container" className="home">Hello</h1>;
Copy the code
/** @jsx h */
const element = h("h1", {
  id: "container".className: "home"
}, "Hello");
Copy the code

Now let’s start creating our own H function!

function h(nodeName, attributes, ... args) {
  // Concat is used for flattening args because the elements in args arrays may also be arrays
  / / h (' div ', {}, [1, 2, 3]) h (' d ', {}, 1, 2, 3) are legitimate calls
  constchildren = args.length ? [].concat(... args) :null;

  return { nodeName, attributes, children };
}

Copy the code
const vnode = h("div", {
  id: "urusai"
}, "Hello!");

/ / return
/ / {
// "nodeName": "div",
// "attributes": {
// "id": "urusai"
/ /},
// "children": [
// "Hello!"
/ /]
// }
Copy the code

H returns a vNode. With a vnode, we also need to convert the vNode into a real DOM:

function render(vnode) {
  if (typeof vnode === 'string') {
    // Generate a text node
    return document.createTextNode(vnode);
  }

  // Generate element nodes and set attributes
  const node = document.createElement(vnode.nodeName);
  const attributes = vnode.attributes || {};
  Object.keys(attributes).forEach(key= > node.setAttribute(key, attributes[key]));

  if (vnode.children) {
    // Recursively call render to generate child nodes
    vnode.children.forEach(child= > node.appendChild(render(child)));
  }

  return node;
}
Copy the code

Now let’s use these two methods:

/** @jsx h */
const vnode = <div id="urusai">Hello!</div>;
const node = render(vnode);
document.body.appendChild(node);
Copy the code

After compiling and transcoding:

/** @jsx h */
const vnode = h("div", {
  id: "urusai"
}, "Hello!");
const node = render(vnode);
document.body.appendChild(node);
Copy the code

We can also iterate over groups of numbers:

/** @jsx h */
const items = ['baga'.'hentai'.'urusai'];
const vnode = <ul>{items.map((item, index) => <li key={index}>{item}</li>)}</ul>;
const list = render(vnode);
document.body.appendChild(list);
Copy the code

After compiling and transcoding:

/** @jsx h */
const items = ['baga'.'hentai'.'urusai'];
const vnode = h("ul".null, items.map((item, index) = > h("li", {
  key: index
}, item)));
const list = render(vnode);
document.body.appendChild(list);
Copy the code

With h render two functions, we have implemented a very simple JSX renderer!!

reference

  • WTF is JSX

  • JSX In Depth

  • React Without JSX