重拾JSX
JSX是一種JavaScript的語法拓展,可以使用它來進行UI的展示:
const element = <h1>Hello, world!</h1>; 複製程式碼
我們一般會在元件的 render
方法裡使用JSX進行佈局和事件繫結:
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而存在的語法糖
我們在平時的元件編寫中,通常都這麼寫:
import React, { Component } from 'react'; class Demo extends Component { render() { return ( <h1>Hello, world!</h1> ) } } 複製程式碼
然而程式碼裡面並沒有用到React,為什麼要引入這個變數呢?
因為JSX是 React.createElement
這個方法的語法糖:
const element = <h1 id="container" className="home">Hello</h1>; // 等價於 const element = React.createElement("h1", { id: "container", className: "home" }, "Hello"); 複製程式碼
推薦大家在babeljs.io上看下JSX編譯後的實際效果

React.createElement有三個引數:
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> ); } } 複製程式碼
編譯後:
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-react
用 h
方法名代替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"); 複製程式碼

現在讓我們開始建立自己的 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渲染器!!!