如果你在兩個月前問我對React的看法,我很可能這樣說:
- 我的模板在哪裡?javascript中的HTML在做些什麼瘋狂的事情?JSX開起來非常奇怪!快向它開火,消滅它吧!
那是因為我沒有理解它.
我發誓,React 無疑是在正確的軌道上, 請聽我道來.
Good old MVC
在一個互動式應用程式一切罪惡的根源是管理狀態。
“傳統”的方式是MVC架構,或者一些變體。
MVC提出你的模型是檢驗真理的唯一來源 - 所有的狀態住在那裡。
視圖是源自模型,並且必須保持同步。
當模式的轉變,所以沒有查看。
最後,使用者互動是由控制器,它更新模型抓獲。
到目前為止,一切都很好。
模型發生變化時就要對視圖進行渲染
這看起來相當簡單。首先,我們需要描述視圖——它是如何將模型狀態轉換到DOM上去的。然後,使用者一發生了什麼操作我們就要對模型進行更新,並且要對整個頁面進行重新渲染... 對不? 沒這麼快哦. 不幸的事,這其實並沒有這麼直接,因為如下兩個原因:
- DOM實際上有某種狀態,就比如一個文本輸入框中的內容. 如果你完全作廢你的DOM來進行重新渲染,這樣的內容會丟失掉.
- DOM 操作 (像刪除和插入節點) 真的慢. 頻繁的渲染會導致嚴重的效能問題.
那麼我們如果在避免這些問題的前提下保持模型和視圖同步呢?
資料繫結
過去三年,被引進用來解決這個問題最常用多架構功能就是資料繫結.
資料繫結能自動地保持模型和視圖的同步. 通常在JavaScript中就代表了對象和DOM.
它會通過讓你聲明應用中各個塊之間的依賴來對這一同步進行打包。狀態的變化會在整個應用程式中蔓延,然後所有的依賴塊都會被自動更新.
讓我們來看看一些有名的架構中它實際是如何運作的吧.
Knockout
Knockout 主張使用的是 MVVM (模型-視圖-視圖模型) 方法,並且幫你實現了“視圖”的部分:
// 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中發生變化。你從來都不需要寫代碼將其進行綁定。這多酷啊,呵?
但是等等,模型不是真相的來源麼? 這裡的視圖模型從來獲得它的狀態呢? 它是怎麼知道模型發生了變化的呢? 有趣的問題啊.
Angular
Angular 採用保持模型和視圖同步的方式描述了資料繫結. 文檔時這麼描述的:
但是... 視圖應該直接通模型打交道麼? 這樣它們不久緊緊的耦合起來了麼?
不管怎麼樣,我們還是來義務地看看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; };});
從這個樣本中,看起來像是控制器有了狀態,並且有類似模型的行為 - 或者也許是一個視圖模型? 假設模型在其它的地方, 那它是如何保持與控制器的同步的呢?
我的頭開始有點兒疼了.
資料繫結的問題
資料繫結在小的例子中運行起來很不錯。不過,隨著你的應用規模變大,你可能會遇到下面這些問題.
聲明的依賴會很快引入迴圈
最經常要處理的問題就是對付狀態中變化的副作用。這張圖來自 Flux 介紹,它解釋了依賴是如何開始挖坑的:
你能預計到當一個模型發生變化時跟著會發生什麼改變麼? 當依賴發生變化時,對於可以任意次序執行的代碼你很難推理出問題的起因。
模板和展示邏輯被人為的分離
視圖扮演了什麼角色呢? 它扮演的就是向使用者展示資料的角色。視圖模型扮演的角色又是什麼呢? 它扮演的也是向使用者展示資料的角色?有啥不同?完全沒有!
最後,視圖組件應該能操作其資料並以需要的格式對資料進行展示。然後,所有的範本語言本質上都是有缺陷的:它們從來都不能達到跟代碼一樣的表現力和功能。
很簡單, {{# each}}, ng-repeat 和 databind="foreach" 這些都是針對 JavaScript 中某些原生和瑣碎事務的拙劣替代物。而它們不會更進一步走得更遠。因此它們不會為你提供過濾器或者映射。
資料繫結是應重新渲染而生的小技巧
什麼是聖杯不再我們的討論之列。每個人總是想要得到的是,當狀態發生變化時能重新對整個應用進行渲染。這樣,我們就不用去處理所有麻煩問題的根源了:狀態總是會隨著時間發生變化——給定任何特定的狀態,我們就可以簡單的描述出應用回是什麼樣子的。
好了,問題清楚了。哥們,我希望某些大公司能組個超能天才開發人員團來真正解決這個問題...
擁抱Facebook的React
事實證明他們做到了。React實現了一個虛擬DOM,一種給我們帶來的聖杯的利器.
虛擬DOM是啥東西呢?
很高興你能這麼問?讓我們來看看一個簡單React樣本.
var Hello = React.createClass({ render: function() { return <div>Hello {this.props.name}</div>; }});React.render(<Hello name="World" />, document.getElementById('container'));
這就是一個React組件的所有API。你必須要有一個渲染方法。複雜吧,呵呵?
OK, 但是 <div> 是什麼意思? 那不是 JavaScript 啊! 對了,它就不是.
你的新夥伴,JSX
這段代碼實際上是用 JSX 寫的,它是 JavaScript 的一個超集,包含了用於定義組件的文法。上面的代碼會被編譯成 JavaScript,因此實際上會變成:
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'));
你明白這段對 createElement 調用的代碼麼? 這些對象組成了虛擬 DOM 的實現。
很簡單 : React 首先在記憶體中對應用的整個結構進行了組裝。然後它會把這個結構裝換成實際的 DOM 節點並將其插入瀏覽器的 DOM 中。
OK,但是用這些奇怪的 createElement 函數編寫 HTML 的目的是什麼呢?
虛擬DOM就是快
我們已經討論過, 操作 DOM 消耗大得離譜,因此它必須以儘可能少的時間完成。
React 的虛擬 DOM 使得兩棵 DOM 結構的比對真正快起來,並且能確切的找到它們之間有什麼變化. 如此,React 就能計算出更新 DOM 所需要做出的最小變更。
實話說,React 能比對兩棵 DOM 樹,找出它所要執行的最小操作集。這有兩個意義:
- 如果一個帶有文本的輸入框被重新渲染,React 會知道它有的內容, 它不會碰那個碰那個輸入框。不會有狀態發生丟失的!
- 比對虛擬 DOM 開銷一點也不昂貴,因此我們想怎麼比對都可以。當其準備好要對 DOM 進行實際的修改時,它只會進行最少量的操作。沒有額外的拖慢布局之虞!
那我們還要在狀態發生變化時記住這兩個對整個 app 進行重新渲染的問題麼?
這都是過去式了。
React 將狀態映射到 DOM
React 中只有虛擬 DOM 的渲染和比對是神奇的部分。其優秀效能是使得我們擁有簡化了許多的整理架構的基礎。有多簡單呢?
React 組件都是等冪(一個等冪操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同)的函數。它們能在任意一個即時的點來描述你的UI。~ Pete Hunt, React: 對最佳實務的重新思考
簡單的等冪函數。
React 組件整個就是這麼一個東西,真的。它將當前的應用狀態映射到了 DOM。並且你也擁有JavaScript的全部能力去描述你的 UI——迴圈,函數,範圍,組合,模組 - 不是一個蹩腳的範本語言哦.
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'));
今天就開始使用 React
React 一開始會有點令人生畏。它提出了一個實在是太大了點的模式轉變,這總有點令人不舒服。不過,當你開始使用它時其優勢會變得清楚起來。
React 文檔很優秀. 你應該照著教程對其進行一下嘗試。我確信如果你給它一個機會,你肯定會愛上她。
編碼快樂!