
之前一直用 React.js 來(lái)寫(xiě)一個(gè)電影選購(gòu)商城,對(duì)于剛上手的我來(lái)說(shuō)感覺(jué)處處和 Vue.js 差不多呀,反正就是無(wú)腦使用數(shù)據(jù)綁定,然后 render 就收工了,一直沒(méi)有去了解 React 誕生的原因。如果你也一直用著 React.js 但是不了解它的歷史,那么希望這篇文章可以幫到你。
簡(jiǎn)單的加減器
我們先來(lái)做一個(gè)簡(jiǎn)音的加減器,要求
- 顯示一個(gè)數(shù)字
- 點(diǎn)擊按鈕+,數(shù)字加1
- 點(diǎn)擊按鈕-,數(shù)字減1
使用原生的 DOM API,我們可能會(huì)寫(xiě)出這樣的代碼:
<div>
<span id="result">0</span>
<button id="add">+</button>
<button id="minus">-</button>
</div>
let result = document.querySelector('#result')
let add = document.querySelector('#add')
let minus = document.querySelector('#minus')
add.addEventListener('click', function () {
// Turn to number
let number = parseInt(result.innerText)
number += 1
result.innerText = number
})
minus.addEventListener('click', function () {
// Turn to number
let number = parseInt(result.innerText)
number -= 1
result.innerText = number
})
這里的代碼無(wú)非就是
- 先獲取要用的元素
- 每次點(diǎn)擊先獲取值,然后進(jìn)行處理(加減法)
- 最后再賦上新值
這就是我們經(jīng)常說(shuō)的“意大利面條”代碼,跟語(yǔ)文老師說(shuō)的“你的文章寫(xiě)得跟流水賬一樣”。怎么才能避免這樣的寫(xiě)法呢?答案:將代碼抽象。
思路
我們現(xiàn)在的思路就是下圖這樣的。

React 程序員這時(shí)候想要是能把上面的獲取步驟去掉就好了:
- 我不從 DOM 元素里取,而是在 JS 里直接生成一個(gè) DOM 元素
- 每次值改變的時(shí)候,重新將這個(gè)元素渲染(清除,修改,追加)
虛擬 DOM
生成 DOM 元素當(dāng)然不是就用 document.createElement 那么簡(jiǎn)單了,我們希望創(chuàng)建 DOM 的時(shí)候把其下面的子元素和屬性都帶上一并創(chuàng)建,一個(gè)好的創(chuàng)建函數(shù)可以是這樣的:
createElement(RootElement root, Attributes {}, Children [])
有了這個(gè)創(chuàng)建元素的函數(shù)后我們就可以不用在 HTML 里寫(xiě)元素了,直接用這個(gè)函數(shù)創(chuàng)建就好。
<div id="root"></div>
這樣寫(xiě)JS就更容易理解了。
let number = 0
let add = () => {
number += 1
render()
}
let minus = () => {
number -= 1
render()
}
let render = () => {
let span = React.createElement('span', { className: 'red' }, number)
let addBtn = React.createElement('button', {onClick: add}, '+')
let minusBtn = React.createElement('button', {onClick: minus}, '-')
let div = React.createElement('div', { className: 'parent' }, span, addBtn, minusBtn)
ReactDOM.render(div, document.querySelector('#root'))
}
render()
現(xiàn)在我們的思路是:點(diǎn)擊 => 修改數(shù)據(jù) => 渲染,去掉了取值的操作。
JSX 的發(fā)明
這時(shí)候 React 程序員還是覺(jué)得不好,這一大坨的React.createElement很zz,所以他們做了下面的3件事:
- 先將
React.createElement用h來(lái)代替,即let h = React.createElement。嗯,這樣代碼縮短了一點(diǎn)點(diǎn) - 將一次使用過(guò)的變量直接傳入創(chuàng)建
div的React.createElement里,所以變成這樣
let div =
h( 'div', { className: 'parent' },
h('span', { className: 'red' }, number),
h('button', {onClick: add}, '+'),
h('button', {onClick: minus}, '-'))
- 這時(shí)候有沒(méi)有突然發(fā)現(xiàn)這樣的代碼和我們的 HTML 有點(diǎn)像啊?
div是父元素,其它的都是子元素,只要縮進(jìn)縮得好,一切好像都能用 HTML 代碼來(lái)表示
let trans = `
<div class="parent">
<span class="red">number</span>
<button onclick="add">+</button>
<button onclick="minus">-</button>
</div>`
所以 JSX 就被發(fā)明出來(lái)了,為了能夠區(qū)分變量和字符串,變量要用{}來(lái)包住變量,而且為了和 HTML 區(qū)分 onclick 變成 onClick、class 變成 className
let jsx = `
<div className="parent">
<span className="red">{number}</span>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>`
看看用 Babel 翻譯后的結(jié)果

最終React版本
直接使用 JSX 語(yǔ)法就是我們 React 一直使用的樣子了。
let number = 0
let add = () => {
number += 1
render()
}
let minus = () => {
number -= 1
render()
}
let render = () => {
ReactDOM.render(
<div className="parent">
<span className="red">{number}</span>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>,
document.querySelector('#root'))
}
render()
很多人開(kāi)始學(xué) React 的時(shí)候一直覺(jué)得虛擬 DOM 是什么高大上的東西,其實(shí)簡(jiǎn)單來(lái)說(shuō)就是假的 DOM。為什么呢?因?yàn)椴贿^(guò)是通過(guò)對(duì)象來(lái)創(chuàng)建 DOM 元素罷了,如
let attr = {
className: "parent",
text: 'hello'
}
React.createElement(root, attr, childElement)
所以虛擬 DOM 其實(shí)就是表示真實(shí) DOM 元素的對(duì)象而已。
(完)