重拾JSX

React.createElement語法糖

JSX是一種JavaScript的語法拓展,可使用它來進行UI的展現:javascript

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

咱們通常會在組件的render方法裏使用JSX進行佈局和事件綁定:html

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

React的核心機制之一就是能夠建立虛擬的DOM元素,利用虛擬DOM來減小對實際DOM的操做從而提高性能,JSX正是爲了虛擬DOM而存在的語法糖java

咱們在平時的組件編寫中,一般都這麼寫:node

import React, { Component } from 'react';

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

然而代碼裏面並無用到React,爲何要引入這個變量呢?react

由於JSX是React.createElement這個方法的語法糖:webpack

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

// 等價於
const element = React.createElement("h1", {
  id: "container",
  className: "home"
}, "Hello");

推薦你們在babeljs.io上看下JSX編譯後的實際效果
jsxweb

React.createElement有三個參數:api

React.createElement(
  type, // dom類型,好比div,h1
  [props], // dom屬性,好比id,class,事件
  [...children] // 子節點,字符串或者React.createElement生成的一個對象
)

JSX用一種相似HTML的語法替代了比較繁瑣的React.createElement純JS方法,而@babel/preset-react插件就起到了最關鍵的一步:負責在webpack編譯時,把全部的JSX都改爲React.createElement:數組

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

編譯後:babel

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

在開發中,有了JSX後咱們基本不怎麼須要用到createElement方法,但若是咱們須要實現這樣一個組件:

// 根據傳入的type屬性,渲染成相應的html元素
<Tag type="h1" id="hello" onClick={() => console.log('hello')}>this is a h1</Tag>
<Tag type="p">this is a p</Tag>

咱們不太可能根據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>
  }
}

這時,就須要用到底層的api了:

function Tag(props) {
  const { type, ...other } = props;

  return React.createElement(type, other, props.children);
}

本身實現一個JSX渲染器

虛擬dom本質就是一個js對象:

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

能夠經過在每一個文件的上方添加/** @jsx h */來告訴@babel/preset-reacth方法名代替JSX(默認方法是React.createElement)

/** @jsx h */

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

jsx2

如今讓咱們開始建立本身的h函數吧!

function h(nodeName, attributes, ...args) {
  // 使用concat是爲了扁平化args,由於args數組裏面的元素可能也是數組
  // h('div', {}, [1, 2, 3])  h('d', {}, 1, 2, 3) 都是合法的調用
  const children = args.length ? [].concat(...args) : null;

  return { nodeName, attributes, children };
}
const vnode = h("div", {
  id: "urusai"
}, "Hello!");

// 返回
// {
//  "nodeName": "div",
//  "attributes": {
//   "id": "urusai"
//  },
//  "children": [
//   "Hello!"
//  ]
// }

h的做用就是返回一個vnode,有了vnode,咱們還須要把vnode轉成真實的dom:

function render(vnode) {
  if (typeof vnode === 'string') {
    // 生成文本節點
    return document.createTextNode(vnode);
  }

  // 生成元素節點並設置屬性
  const node = document.createElement(vnode.nodeName);
  const attributes = vnode.attributes || {};
  Object.keys(attributes).forEach(key => node.setAttribute(key, attributes[key]));

  if (vnode.children) {
    // 遞歸調用render生成子節點
    vnode.children.forEach(child => node.appendChild(render(child)));
  }

  return node;
}

如今讓咱們使用這兩個方法吧:

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

編譯轉碼後:

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

咱們還能夠遍歷數組:

/** @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);

編譯轉碼後:

/** @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);

經過h render兩個函數,咱們就實現了一個很簡單的JSX渲染器!!!

參考

相關文章
相關標籤/搜索