看源碼一個痛處是會陷進理不順主幹的困局中,本系列文章在實現一個 (x)react 的同時理順 React 框架的主幹內容(JSX/虛擬DOM/組件/生命週期/diff算法/setState/ref/...)html
項目打包工具選擇了 parcel,使用其能夠快速地進入項目開發的狀態。快速開始node
此外須要安裝如下 babel 插件:react
"babel-core": "^6.26.0", "babel-preset-env": "^1.6.1", "babel-plugin-transform-react-jsx": "^6.24.1"
同時 .babelrc
配置以下:git
{ "presets": ["env"], "plugins": [ // 插件如其名:轉化 JSX 語法爲定義的形式 ["transform-react-jsx", { "pragma": "React.createElement" }] ] }
const element = ( <div className="title"> hello<span className="content">world!</span> </div> )
JSX 是一種語法糖,通過 babel 轉換結果以下,能夠發現實際上轉化成 React.createElement()
的形式:github
var element = React.createElement( "div", { className: "title" }, "hello", React.createElement( "span", { className: "content" }, "world!" ) );
打印 element, 結果以下:算法
{ attributes: {className: "title"} children: ["hello", t] // t 和外層對象相同 key: undefined nodeName: "div" }
所以,咱們得出結論:JSX 語法糖通過 Babel 編譯後轉換成一種對象,該對象即所謂的虛擬 DOM
,使用虛擬 DOM 能讓頁面進行更爲高效的渲染。babel
咱們按照這種思路進行函數的構造:app
const React = { createElement } function createElement(tag, attr, ...child) { return { attributes: attr, children: child, key: undefined, nodeName: tag, } } // 測試 const element = ( <div className="title"> hello<span className="content">world!</span> </div> ) console.log(element) // 打印結果符合預期 // { // attributes: {className: "title"} // children: ["hello", t] // t 和外層對象相同 // key: undefined // nodeName: "div" // }
上個小節介紹了 JSX 轉化爲虛擬 DOM 的過程,這個小節接着來實現將虛擬 DOM 轉化爲真實 DOM (頁面上渲染的是真實 DOM)。框架
咱們知道在 React 中,將虛擬 DOM 轉化爲真實 DOM 是使用 ReactDOM.render
實現的,使用以下:dom
ReactDOM.render( element, // 上文的 element,即虛擬 dom document.getElementById('root') )
接着來實現 ReactDOM.render
的邏輯:
const ReactDOM = { render } /** * 將虛擬 DOM 轉化爲真實 DOM * @param {*} vdom 虛擬 DOM * @param {*} container 須要插入的位置 */ function render(vdom, container) { if (typeof(vdom) === 'string') { container.innerText = vdom return } const dom = document.createElement(vdom.nodeName) for (let attr in vdom.attributes) { setAttribute(dom, attr, vdom.attributes[attr]) } vdom.children.forEach(vdomChild => render(vdomChild, dom)) container.appendChild(dom) } /** * 給節點設置屬性 * @param {*} dom 操做元素 * @param {*} attr 操做元素屬性 * @param {*} value 操做元素值 */ function setAttribute(dom, attr, value) { if (attr === 'className') { attr = 'class' } if (attr.match('/on\w+/')) { // 處理事件的屬性: const eventName = attr.toLowerCase().splice(1) dom.addEventListener(eventName, value) } else if (attr === 'style') { // 處理樣式的屬性: let styleStr = '' let standardCss for (let klass in value) { standardCss = humpToStandard(klass) // 處理駝峯樣式爲標準樣式 styleStr += `${standardCss}: ${value[klass]};` } dom.setAttribute(attr, styleStr) } else { // 其它屬性 dom.setAttribute(attr, value) } }
至此,咱們成功將虛擬 DOM 復原爲真實 DOM,展現以下:
另外配合熱更新,在熱更新的時候清空以前的 dom 元素,改動以下:
const ReactDOM = { render(vdom, container) { container.innerHTML = null render(vdom, container) } }
JSX
通過 babel 編譯爲 React.createElement() 的形式,其返回結果就是 Virtual DOM
,最後經過 ReactDOM.render() 將 Virtual DOM 轉化爲真實的 DOM 展示在界面上。流程圖以下:
以下是一個 react/preact 的經常使用組件的寫法,那麼爲何要 import 一個 React 或者 h 呢?
import React, { Component } from 'react' // react // import { h, Component } from 'preact' // preact class A extends Component { render() { return <div>I'm componentA</div> } } render(<A />, document.body) // 組件的掛載
該系列文章會盡量的分析項目細節,具體的仍是以項目實際代碼爲準。