深入理解JavaScript的React框架的原理
如果你在兩個(gè)月前問(wèn)我對(duì)React的看法,我很可能這樣說(shuō):
- 我的模板在哪里?javascript中的HTML在做些什么瘋狂的事情?JSX開(kāi)起來(lái)非常奇怪!快向它開(kāi)火,消滅它吧!
那是因?yàn)槲覜](méi)有理解它.
我發(fā)誓,React 無(wú)疑是在正確的軌道上, 請(qǐng)聽(tīng)我道來(lái).
Good old MVC
在一個(gè)交互式應(yīng)用程序一切罪惡的根源是管理狀態(tài)。
“傳統(tǒng)”的方式是MVC架構(gòu),或者一些變體。
MVC提出你的模型是檢驗(yàn)真理的唯一來(lái)源 - 所有的狀態(tài)住在那里。
視圖是源自模型,并且必須保持同步。
當(dāng)模式的轉(zhuǎn)變,所以沒(méi)有查看。
最后,用戶(hù)交互是由控制器,它更新模型抓獲。
到目前為止,一切都很好。

模型發(fā)生變化時(shí)就要對(duì)視圖進(jìn)行渲染
這看起來(lái)相當(dāng)簡(jiǎn)單。首先,我們需要描述視圖——它是如何將模型狀態(tài)轉(zhuǎn)換到DOM上去的。然后,用戶(hù)一發(fā)生了什么操作我們就要對(duì)模型進(jìn)行更新,并且要對(duì)整個(gè)頁(yè)面進(jìn)行重新渲染... 對(duì)不? 沒(méi)這么快哦. 不幸的事,這其實(shí)并沒(méi)有這么直接,因?yàn)槿缦聝蓚€(gè)原因:
- DOM實(shí)際上有某種狀態(tài),就比如一個(gè)文本輸入框中的內(nèi)容. 如果你完全作廢你的DOM來(lái)進(jìn)行重新渲染,這樣的內(nèi)容會(huì)丟失掉.
- DOM 操作 (像刪除和插入節(jié)點(diǎn)) 真的慢. 頻繁的渲染會(huì)導(dǎo)致嚴(yán)重的性能問(wèn)題.
那么我們?nèi)绻诒苊膺@些問(wèn)題的前提下保持模型和視圖同步呢?
數(shù)據(jù)綁定
過(guò)去三年,被引進(jìn)用來(lái)解決這個(gè)問(wèn)題最常用多框架功能就是數(shù)據(jù)綁定.
數(shù)據(jù)綁定能自動(dòng)地保持模型和視圖的同步. 通常在JavaScript中就代表了對(duì)象和DOM.
它會(huì)通過(guò)讓你聲明應(yīng)用中各個(gè)塊之間的依賴(lài)來(lái)對(duì)這一同步進(jìn)行打包。狀態(tài)的變化會(huì)在整個(gè)應(yīng)用程序中蔓延,然后所有的依賴(lài)塊都會(huì)被自動(dòng)更新.
讓我們來(lái)看看一些有名的框架中它實(shí)際是如何運(yùn)作的吧.
Knockout
Knockout 主張使用的是 MVVM (模型-視圖-視圖模型) 方法,并且?guī)湍銓?shí)現(xiàn)了“視圖”的部分:
// View (a template)
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
// ViewModel (diplay data... and logic?)
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.pureComputed(function() {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};
而這就是了. 不管改變那邊的輸入值都在讓span中發(fā)生變化。你從來(lái)都不需要寫(xiě)代碼將其進(jìn)行綁定。這多酷啊,呵?
但是等等,模型不是真相的來(lái)源么? 這里的視圖模型從來(lái)獲得它的狀態(tài)呢? 它是怎么知道模型發(fā)生了變化的呢? 有趣的問(wèn)題啊.
Angular
Angular 采用保持模型和視圖同步的方式描述了數(shù)據(jù)綁定. 文檔時(shí)這么描述的:

但是... 視圖應(yīng)該直接通模型打交道么? 這樣它們不久緊緊的耦合起來(lái)了么?
不管怎么樣,我們還是來(lái)義務(wù)地看看hello world示例吧:
// View (a template)
<div ng-controller="HelloController as hello">
<label>Name:</label>
<input type="text" ng-model="hello.firstName">
<input type="text" ng-model="hello.lastName">
<h1>Hello {{hello.fullName()}}!</h1>
</div>
// Controller
angular.module('helloApp', [])
.controller('HelloController', function() {
var hello = this;
hello.fullName = function() {
return hello.firstName + hello.lastName;
};
});
從這個(gè)示例中,看起來(lái)像是控制器有了狀態(tài),并且有類(lèi)似模型的行為 - 或者也許是一個(gè)視圖模型? 假設(shè)模型在其它的地方, 那它是如何保持與控制器的同步的呢?
我的頭開(kāi)始有點(diǎn)兒疼了.
數(shù)據(jù)綁定的問(wèn)題
數(shù)據(jù)綁定在小的例子中運(yùn)行起來(lái)很不錯(cuò)。不過(guò),隨著你的應(yīng)用規(guī)模變大,你可能會(huì)遇到下面這些問(wèn)題.
聲明的依賴(lài)會(huì)很快引入循環(huán)
最經(jīng)常要處理的問(wèn)題就是對(duì)付狀態(tài)中變化的副作用。這張圖來(lái)自 Flux 介紹,它解釋了依賴(lài)是如何開(kāi)始挖坑的:

你能預(yù)計(jì)到當(dāng)一個(gè)模型發(fā)生變化時(shí)跟著會(huì)發(fā)生什么改變么? 當(dāng)依賴(lài)發(fā)生變化時(shí),對(duì)于可以任意次序執(zhí)行的代碼你很難推理出問(wèn)題的起因。
模板和展示邏輯被人為的分離
視圖扮演了什么角色呢? 它扮演的就是向用戶(hù)展示數(shù)據(jù)的角色。視圖模型扮演的角色又是什么呢? 它扮演的也是向用戶(hù)展示數(shù)據(jù)的角色?有啥不同?完全沒(méi)有!
- 毫無(wú)疑問(wèn),模板割裂了計(jì)數(shù) ~ Pete Hunt
最后,視圖組件應(yīng)該能操作其數(shù)據(jù)并以需要的格式對(duì)數(shù)據(jù)進(jìn)行展示。然后,所有的模板語(yǔ)言本質(zhì)上都是有缺陷的:它們從來(lái)都不能達(dá)到跟代碼一樣的表現(xiàn)力和功能。
很簡(jiǎn)單, {{# each}}, ng-repeat 和 databind="foreach" 這些都是針對(duì) JavaScript 中某些原生和瑣碎事務(wù)的拙劣替代物。而它們不會(huì)更進(jìn)一步走得更遠(yuǎn)。因此它們不會(huì)為你提供過(guò)濾器或者映射。
數(shù)據(jù)綁定是應(yīng)重新渲染而生的小技巧
什么是圣杯不再我們的討論之列。每個(gè)人總是想要得到的是,當(dāng)狀態(tài)發(fā)生變化時(shí)能重新對(duì)整個(gè)應(yīng)用進(jìn)行渲染。這樣,我們就不用去處理所有麻煩問(wèn)題的根源了:狀態(tài)總是會(huì)隨著時(shí)間發(fā)生變化——給定任何特定的狀態(tài),我們就可以簡(jiǎn)單的描述出應(yīng)用回是什么樣子的。
好了,問(wèn)題清楚了。哥們,我希望某些大公司能組個(gè)超能天才開(kāi)發(fā)者團(tuán)來(lái)真正解決這個(gè)問(wèn)題...
擁抱Facebook的React
事實(shí)證明他們做到了。React實(shí)現(xiàn)了一個(gè)虛擬的DOM,一種給我們帶來(lái)的圣杯的利器.
虛擬的DOM是啥東西呢?
很高興你能這么問(wèn)?讓我們來(lái)看看一個(gè)簡(jiǎn)單React示例.
var Hello = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.render(<Hello name="World" />, document.getElementById('container'));
這就是一個(gè)React組件的所有API。你必須要有一個(gè)渲染方法。復(fù)雜吧,呵呵?
OK, 但是 <div> 是什么意思? 那不是 JavaScript 啊! 對(duì)了,它就不是.
你的新伙伴,JSX
這段代碼實(shí)際上是用 JSX 寫(xiě)的,它是 JavaScript 的一個(gè)超集,包含了用于定義組件的語(yǔ)法。上面的代碼會(huì)被編譯成 JavaScript,因此實(shí)際上會(huì)變成:
var Hello = React.createClass({displayName: "Hello",
render: function() {
return React.createElement("div", null, "Hello ", this.props.name);
}
});
React.render(React.createElement(Hello, {name: "World"}), document.getElementById('container'));
你明白這段對(duì) createElement 調(diào)用的代碼么? 這些對(duì)象組成了虛擬 DOM 的實(shí)現(xiàn)。
很簡(jiǎn)單 : React 首先在內(nèi)存中對(duì)應(yīng)用的整個(gè)結(jié)構(gòu)進(jìn)行了組裝。然后它會(huì)把這個(gè)結(jié)構(gòu)裝換成實(shí)際的 DOM 節(jié)點(diǎn)并將其插入瀏覽器的 DOM 中。
OK,但是用這些奇怪的 createElement 函數(shù)編寫(xiě) HTML 的目的是什么呢?
虛擬的DOM就是快
我們已經(jīng)討論過(guò), 操作 DOM 消耗大得離譜,因此它必須以盡可能少的時(shí)間完成。
React 的虛擬 DOM 使得兩棵 DOM 結(jié)構(gòu)的比對(duì)真正快起來(lái),并且能確切的找到它們之間有什么變化. 如此,React 就能計(jì)算出更新 DOM 所需要做出的最小變更。
實(shí)話(huà)說(shuō),React 能比對(duì)兩棵 DOM 樹(shù),找出它所要執(zhí)行的最小操作集。這有兩個(gè)意義:
- 如果一個(gè)帶有文本的輸入框被重新渲染,React 會(huì)知道它有的內(nèi)容, 它不會(huì)碰那個(gè)碰那個(gè)輸入框。不會(huì)有狀態(tài)發(fā)生丟失的!
- 比對(duì)虛擬 DOM 開(kāi)銷(xiāo)一點(diǎn)也不昂貴,因此我們想怎么比對(duì)都可以。當(dāng)其準(zhǔn)備好要對(duì) DOM 進(jìn)行實(shí)際的修改時(shí),它只會(huì)進(jìn)行最少量的操作。沒(méi)有額外的拖慢布局之虞!
那我們還要在狀態(tài)發(fā)生變化時(shí)記住這兩個(gè)對(duì)整個(gè) app 進(jìn)行重新渲染的問(wèn)題么?
這都是過(guò)去式了。
React 將狀態(tài)映射到 DOM
React 中只有虛擬 DOM 的渲染和比對(duì)是神奇的部分。其優(yōu)秀性能是使得我們擁有簡(jiǎn)化了許多的整理架構(gòu)的基礎(chǔ)。有多簡(jiǎn)單呢?
React 組件都是冪等(一個(gè)冪等操作的特點(diǎn)是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同)的函數(shù)。它們能在任意一個(gè)實(shí)時(shí)的點(diǎn)來(lái)描述你的UI。~ Pete Hunt, React: 對(duì)最佳實(shí)踐的重新思考
簡(jiǎn)單的冪等函數(shù)。
React 組件整個(gè)就是這么一個(gè)東西,真的。它將當(dāng)前的應(yīng)用狀態(tài)映射到了 DOM。并且你也擁有JavaScript的全部能力去描述你的 UI——循環(huán),函數(shù),作用域,組合,模塊 - 不是一個(gè)蹩腳的模板語(yǔ)言哦.
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function (comment) {
return ( <Comment author={comment.author}>
{comment.text} </Comment>
);
});
return ( <div className="commentList">
{commentNodes} </div>
);
}
});
var CommentBox = React.createClass({
render: function() {
return ( <div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.props.data} />
</div>
);
}
});
React.render(
<CommentBox data={data} />,
document.getElementById('content')
);
今天就開(kāi)始使用 React
React 一開(kāi)始會(huì)有點(diǎn)令人生畏。它提出了一個(gè)實(shí)在是太大了點(diǎn)的模式轉(zhuǎn)變,這總有點(diǎn)令人不舒服。不過(guò),當(dāng)你開(kāi)始使用它時(shí)其優(yōu)勢(shì)會(huì)變得清楚起來(lái)。
React 文檔很優(yōu)秀. 你應(yīng)該照著教程對(duì)其進(jìn)行一下嘗試。我確信如果你給它一個(gè)機(jī)會(huì),你肯定會(huì)愛(ài)上她。
編碼快樂(lè)!
相關(guān)文章
使用JavaScript制作一個(gè)簡(jiǎn)單的計(jì)數(shù)器的方法
這篇文章主要介紹了使用JavaScript制作一個(gè)簡(jiǎn)單的計(jì)數(shù)器的方法,用于計(jì)算網(wǎng)頁(yè)用戶(hù)的來(lái)訪(fǎng)次數(shù),需要的朋友可以參考下2015-07-07
Array.slice()與Array.splice()的返回值類(lèi)型
Array.slice()與Array.splice()的返回值類(lèi)型...2006-10-10
Javascript入門(mén)學(xué)習(xí)第四篇 js對(duì)象和數(shù)組
上篇文章講了js中的變量,表達(dá)式,和運(yùn)算符 還有一些 js 語(yǔ)句. 這章我們來(lái)探討js中的對(duì)象和數(shù)組。2008-07-07
JavaScript學(xué)習(xí)筆記(一) js基本語(yǔ)法
JavaScript學(xué)習(xí)筆記(一) js基本語(yǔ)法,想要學(xué)習(xí)js的朋友可以參考下。2011-10-10
Javascript基礎(chǔ)教程之定義和調(diào)用函數(shù)
這篇文章主要介紹了Javascript基礎(chǔ)教程之定義和調(diào)用函數(shù)的相關(guān)資料,需要的朋友可以參考下2015-01-01

