原文鏈接:http://mp.weixin.qq.com/s?__biz=MzA5Njc3Njk5NA==&mid=2650528748&idx=1&sn=4d3093e963ce76c642eb0c5d9a97625b#rd
React的起源
React來自於Facebook,是的,就是那個你們聽說過但是打不開的網站。Facebook的開發者當時在開發一個廣告系統,因為對當前所有的MVC框架不滿意,所以就自己寫了一個UI框架,於是就有了React。后來因為覺得實在是好用,所以在2013年月份開源的此框架。經過這幾年的沉淀,React越來越強大,也受到了越來越多的開發者喜愛。React目前(2015-05-04)的版本是0.14.0,從版本號上看還沒有達到1.0版,意味着React還在頻繁地修改,普遍應用於產品中還需要一定的時間。2015年三月份的F8開發者大會上,Facebook又發布了React Native,正式把React的觸角伸向了APP。同時還為React native開發了一款基於Atom的IDE-Nuclide,也是開源。
React來勢洶洶,大有一統江湖的氣勢。前端開發者應該保持學習新技術的熱情,很有必要熟悉React相關技術。下面我們簡要談談React相關的技術。
React的設計思想
熟悉一個新技術的關鍵是熟悉他的特色和理念
React框架本身和我們常用的JavaScript MVC框架,如:AngularJS,Backbone,Ember等,沒有直接的可比性。在React的官方博客中明確闡述了React不是一個MVC框架,而是一個用於構建組件化UI的庫,是一個前端界面開發工具。所以頂多算是MVC中的V(view)。React並沒有重復造輪子,而是有很多顛覆性的創新,具體的特性如下:
編寫簡單直觀的代碼
在年初的React開發者大會上,React的項目經理Tom Occhino講述了React的最大的價值,React最大的價值不是高性能的虛擬DOM、封裝的事件機制、服務器端渲染,而是聲明式的直觀的編碼方式。React號稱能讓新人第一天開始使用就能開發新功能。簡單的編碼方式會讓新手能很快地上手,同時也降低了代碼維護的成本。這一特性決定了React能快速引起開發者的興趣並廣泛傳播的基礎。以下是React基於這一理念的具體做法。
簡化可復用的組件
React構建UI是使用組件化的方式,而不是常見的模板。組件並不是一個新概念,它是某個獨立功能或者界面的封裝,達到復用或者UI和業務松耦合的目的。
組件化的設計理念也出現了很多年了,我們常用的ExtJS、YUI、jQueryUI、BootStrap等等都會提供大量的可復用的UI組件。比如在Bootstrap中使用對話框組件:
// 初始化
$('#myModal').modal({
keyboard: false
});
// 顯示
$('#myModal').modal('show');
// 關閉事件
$('#myModal').on('hidden.bs.modal', function (e) {
// do something...
});
可以看到我們常用的這些組件提供了大量的接口和配置,讓開發者選擇合適的使用場景。這些組件的設計復雜,使用也較繁瑣,新人上手有一定的門檻。W3C也在加緊制定Web Components(即組件)的標准,試圖制定一個統一的簡單實用的標准化的組件概念。我們看看React是如何設計組件模型的以及其和Web Component的區別。
React框架里面使用了簡化的組件模型,但更徹底地使用了組件化的概念。React將整個UI上的每一個功能模塊定義成組件,然后將小的組件通過組合或者嵌套的方式構成更大的組件。這種做法已經在instagram網站上普遍實施,大家可以看看instagram的前端源代碼。 如下通過一個簡單的例子來闡述React中模塊化的概念。這個例子來自於React的官方網站教程,是完成一個簡單的評論框。這個評論框主要包含兩個部分,評論列表和評論表單。顯示效果如下:
按照React模塊組合的設計,把評論框組件commentBox分為兩個子組件模塊:commentList和commentForm,代碼如下:
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
commentList和commentForm組件對應的代碼如下:
<div className="commentList">
{commentNodes}
</div>
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder="Your name" ref="author" />
<input type="text" placeholder="Say something..." ref="text" />
<input type="submit" value="Post" />
</form>
從代碼中可以看到CommentList組件又可以划分為comment組件的列表。comment組件代碼如下:
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML= />
</div>
可以看出,為了完成評論框功能,使用React定義了四個不同的組件:commentBox、commentList、commentForm、comment。commentBox是由commentList和commentForm組合而來,commentList是由comment組合而來。這個例子充分體現了React組件化的理念,每個組件的UI和邏輯都定義到了內部,暴露少量的API和外部交互,組件之間組合形成更復雜的組件。總結一下,React的組件具有如下的特性:
-
可組合:簡單組件可以組合為復雜的組件
-
可重用:每個組件都是獨立的,可以被多個組件使用
-
可維護:和組件相關的邏輯和UI都封裝在了組件的內部,方便維護
-
可測試:因為組件的獨立性,測試組件就變得方便很多。
React使用了組件化的設計,所以開發者自然而然和原生的Web Components相提並論,StackExchange上有一篇精彩的討論,解釋了React的組件和原生組件的對比。文章從語言層面、樣式的封裝、數據綁定、DOM操作方式等這幾個方面展開討論,結論是說兩者沒有優劣之分,只是編碼習慣問題。Web Components規范畢竟還在制定過程中,應該可以從React的組件設計方式上借鑒一些理念。在后續的文章中,會深入探討React中組件的設計原理及使用。
虛擬DOM
在JavaScript中DOM操作是獨立成為一個分支的。各瀏覽器在實現DOM操作庫也是大同小異,都是在單獨的模塊中實現了DOM操作,由於各種技術上的原因,DOM操作的性能損耗相對於其他操作是很大的。在前端開發中都是需要特別盡量保持較小的DOM操作次數來提高性能。
React作為一個UI框架,不可避免要有界面上元素的交互。為了提高性能,React在操作頁面交互時引入了虛擬DOM的概念。虛擬DOM是在React中用JavaScript重新實現的一個DOM模型,和原生的DOM並沒有多少關系,只是借鑒了原生DOM的一些概念。虛擬DOM並沒有完全實現DOM,只是保留了元素直接的層級關系和少量必要的屬性。因為減少了不必要的復雜性,實踐校驗的結果是虛擬DOM的性能比原生DOM高很多。來看看普通DOM和虛擬DOM在代碼上的差別。
如下是使用原生DOM生成的元素:
var a = document.createElement('a')
a.setAttribute('class', 'link')
a.setAttribute('href', 'https://github.com/facebook/react')
a.appendChild(document.createTextNode('React'))
那么使用虛擬DOM則代碼為如下:
var a = React.createElement('a', {
className: 'link',
href: 'https://github.com/facebook/react'
}, 'React')
可以看到React中使用了自己實現的createElement
方法來生成元素DOM結構。
基於React開發中構建的DOM都是通過虛擬DOM進行的。在React的實際的使用中,需要根據不同的數據展現不同的UI,當數據變化時,React會重新構建整個DOM樹,然后將當前的DOM樹和之前的比較,得到DOM樹的區別,然后僅僅把變化的部分反映到實際的瀏覽器UI更新上。React會在同一個事件循環內合並DOM的變化,只是會對比開始和結束的DOM變化,忽略中間過程的DOM變化。盡管每次數據變化都是重新構建DOM樹,但虛擬DOM的操作性能極高。這樣使用React時,開發者不在需要關心數據變化時頁面上DOM元素的更新,而只是關心各個數據狀態下頁面實際展現的效果。此外,因為React使用了由JavaScript實現的虛擬DOM,意味着可以在服務器端完成HTML結構的構建。
JSX
JSX是React的重要組成部分,他使用類似XML標記的方式來聲明界面及關系,所以他只是一個文檔規范。如下是一個在React里面使用JSX的例子:
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.render(<HelloMessage name="John" />, mountNode);
可以看到如上使用了JSX的代碼,像是HTML和JavaScript代碼的混合體。很多人很不習慣這樣的編碼方式,認為這和我們一直倡導的表現和邏輯分離的思想相違背,是一種倒退。那么React這樣的設計用意是啥呢?
React一個主要的設計理念是編寫簡單容易理解的代碼。HTML模板的作用是讓表現和邏輯分離,但是很多情況下模板還是嚴重依賴於業務邏輯,兩者沒有辦法做到完全的松耦合。稍微復雜一點的例子,比如AngularJS使用了一套獨特的機制來讓UI和邏輯交互,示例代碼如下。
<ul class="unstyled">
<li ng-repeat="todo in todoList.todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-"></span>
</li>
</ul>
使用AngularJS的確從代碼角度做到表現和邏輯分離,但是在HTML里面混入了大量的屬性標記,這些標記但從語義上很難理解,新手比如要整個熟悉Angular中每個類似ng-*對應的用法及意義才能理解整個邏輯,所以有一定的入門門檻。如上例子使用JSX方式編寫如下:
render: function () {
var lis = this.todoList.todos.map(function (todo) {
return (
<li>
<input type="checkbox" checked={todo.done}>
<span className="done-{todo.done}">{todo.text}</span>
</li>);
});
return (
<ul class="unstyled">
{lis}
</ul>
);
}
可以看到,JSX中除了使用HTML標記之外,並沒有復雜的標記。這種自然而直觀的方式直接降低了React的學習門檻並且讓代碼更容易理解。
JSX只是簡化了React的使用難度,但並不是必須的。在React中也可以不使用JSX,而是使用原生JavaScript的方式編寫代碼。在實際使用過程中也是把JSX轉換成了JavaScript代碼來運行的。React官方網站上提供了一個在線轉換JSX到原生JavaScript代碼的工具,通過這個工具也可以體會JSX使用上的優勢及其內在原理。
Flux
Flux是另外一個獨立於React的架構。之所以說Flux是一個架構而不是框架或者類庫,是因為Flux僅僅用於配合React框架來處理組件和數據之間的交互。簡單來說Flux就是用於管理數據流。和其他MVC框架倡導的雙向數據綁定不同,Flux使用了單向數據綁定的機制,即數據模型到視圖的流動。如下兩個圖展示MVC和Flux之間的差異:
Flux中主要使用了三個概念:Dispatcher、Action和Store。這三個概念區別於MVC的model、view和controller概念,因為MVC中更多的是數據雙向綁定。
Actions是用於傳遞數據給Dispatcher的操作集合。Action可能來自於用戶界面的操作,也可能是服務器端的數據更新。
Dispatcher是一個全局的分發器,接受Action,並傳遞給注冊的回調函數。
Stores包含了應用的狀態及注冊到Dispatcher的回調函數,這些函數用於處理業務邏輯。
和React Views最密切的是Store,React view從Store取得state和其他數據,並更新界面。
總結
從以上的React相關設計可以看出,React是以降低前端開發的復雜度為原則的。使用React編寫的代碼也易於理解,所以適合大規模多人開發,能提高項目的開發效率和質量。