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,并傳遞給注冊的回調函數。
總結
參考連結
- 深入淺出React(一):React的設計哲學 - 簡單之美
- Why did we build React?
- Facebook React 和 Web Components(Polymer)對比優勢和劣勢
- Getting Started with Facebook’s React.js