從 0 到 1 實現(xiàn) React 系列 —— 1.JSX 和 Virtual DOM

image

看源碼一個痛處是會陷進理不順主干的困局中,本系列文章在實現(xiàn)一個 (x)react 的同時理順 React 框架的主干內(nèi)容(JSX/虛擬DOM/組件/生命周期/diff算法/setState/ref/...)

前置準(zhǔn)備

看源碼一個痛處是會陷進理不順主干的困局中非竿,本系列文章在實現(xiàn)一個 (x)react 的同時理順 React 框架的主干內(nèi)容(JSX/虛擬DOM/...)

環(huán)境準(zhǔn)備

項目打包工具選擇了 parcel,使用其可以快速地進入項目開發(fā)的狀態(tài)现拒。快速開始

此外需要安裝以下 babel 插件:

"babel-core": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-plugin-transform-react-jsx": "^6.24.1"

同時 .babelrc 配置如下:

{
    "presets": ["env"],
    "plugins": [
        // 插件如其名:轉(zhuǎn)化 JSX 語法為定義的形式
        ["transform-react-jsx", {
            "pragma": "React.createElement"
        }]
    ]
}

JSX 和 虛擬 DOM

const element = (
  <div className="title">
    hello<span className="content">world!</span>
  </div>
)

JSX 是一種語法糖,經(jīng)過 babel 轉(zhuǎn)換結(jié)果如下,可以發(fā)現(xiàn)實際上轉(zhuǎn)化成 React.createElement() 的形式:

var element = React.createElement(
  "div",
  { className: "title" },
  "hello",
  React.createElement(
    "span",
    { className: "content" },
    "world!"
  )
);

打印 element, 結(jié)果如下:

{
  attributes: {className: "title"}
  children: ["hello", t] // t 和外層對象相同
  key: undefined
  nodeName: "div"
}

因此,我們得出結(jié)論:JSX 語法糖經(jīng)過 Babel 編譯后轉(zhuǎn)換成一種對象痢掠,該對象即所謂的虛擬 DOM,使用虛擬 DOM 能讓頁面進行更為高效的渲染嘲恍。

我們按照這種思路進行函數(shù)的構(gòu)造:

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) // 打印結(jié)果符合預(yù)期
// {
//   attributes: {className: "title"}
//   children: ["hello", t] // t 和外層對象相同
//   key: undefined
//   nodeName: "div"
// }

虛擬 DOM 轉(zhuǎn)化為真實 DOM

上個小節(jié)介紹了 JSX 轉(zhuǎn)化為虛擬 DOM 的過程足画,這個小節(jié)接著來實現(xiàn)將虛擬 DOM 轉(zhuǎn)化為真實 DOM (頁面上渲染的是真實 DOM)。

我們知道在 React 中佃牛,將虛擬 DOM 轉(zhuǎn)化為真實 DOM 是使用 ReactDOM.render 實現(xiàn)的淹辞,使用如下:

ReactDOM.render(
  element, // 上文的 element,即虛擬 dom
  document.getElementById('root')
)

接著來實現(xiàn) ReactDOM.render 的邏輯:

const ReactDOM = {
  render
}

/**
 * 將虛擬 DOM 轉(zhuǎn)化為真實 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)
}

/**
 * 給節(jié)點設(shè)置屬性
 * @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) // 處理駝峰樣式為標(biāo)準(zhǔn)樣式
      styleStr += `${standardCss}: ${value[klass]};`
    }
    dom.setAttribute(attr, styleStr)
  } else {                       // 其它屬性
    dom.setAttribute(attr, value)
  }
}

至此俘侠,我們成功將虛擬 DOM 復(fù)原為真實 DOM象缀,展示如下:

image

另外配合熱更新蔬将,在熱更新的時候清空之前的 dom 元素,改動如下:

const ReactDOM = {
  render(vdom, container) {
    container.innerHTML = null
    render(vdom, container)
  }
}

總結(jié)

JSX 經(jīng)過 babel 編譯為 React.createElement() 的形式攻冷,其返回結(jié)果就是 Virtual DOM娃胆,最后通過 ReactDOM.render() 將 Virtual DOM 轉(zhuǎn)化為真實的 DOM 展現(xiàn)在界面上。流程圖如下:

image

思考題

如下是一個 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) // 組件的掛載

項目說明

該系列文章會盡可能的分析項目細節(jié),具體的還是以項目實際代碼為準(zhǔn)禁谦。

項目地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胁黑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子州泊,更是在濱河造成了極大的恐慌丧蘸,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遥皂,死亡現(xiàn)場離奇詭異力喷,居然都是意外死亡,警方通過查閱死者的電腦和手機演训,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門弟孟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人样悟,你說我怎么就攤上這事拂募。” “怎么了窟她?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵歼秽,是天一觀的道長幅聘。 經(jīng)常有香客問我翁逞,道長蚤假,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任试伙,我火速辦了婚禮嘁信,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疏叨。我一直安慰自己潘靖,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布蚤蔓。 她就那樣靜靜地躺著卦溢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上单寂,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天贬芥,我揣著相機與錄音,去河邊找鬼宣决。 笑死蘸劈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尊沸。 我是一名探鬼主播威沫,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洼专!你這毒婦竟也來了棒掠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤屁商,失蹤者是張志新(化名)和其女友劉穎烟很,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜡镶,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡雾袱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了官还。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谜酒。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妻枕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情粘驰,我是刑警寧澤屡谐,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站蝌数,受9級特大地震影響愕掏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜顶伞,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一饵撑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唆貌,春花似錦滑潘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春粹舵,著一層夾襖步出監(jiān)牢的瞬間钮孵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工眼滤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巴席,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓诅需,卻偏偏與公主長得像漾唉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子诱担,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351