深刻淺出React(一):React的設計哲學 - 簡單之美

編者按:自2013年Facebook發佈以來,React吸引了愈來愈多的開發者,基於它的衍生技術,如React Native、React Canvas等也層出不窮。InfoQ精心策劃「深刻淺出React」系列文章,爲讀者剖析React開發的技術細節。html

React最初來自Facebook內部的廣告系統項目,項目實施過程當中前端開發遇到了巨大挑戰,代碼變得愈來愈臃腫且混亂不堪,難以維護。因而痛定思痛,他們決定拋開不少所謂的「最佳實踐」,從新思考前端界面的構建方式,因而就有了React。前端

React帶來了不少開創性的思路來構建前端界面,雖然選擇React的最重要緣由之一是性能,可是相關技術背後的設計思想更值得咱們去思考。以前我也曾寫過一篇React的入門文章,並提供了示例代碼,你們能夠結合參考。react

上個月React發佈了最新的0.13版,並提供了對ES6的支持。在新版本中,一個小小的改變是React取消了函數的自動綁定,也就是說,之前能夠這樣去綁定一個事件:git


而在以ES6語法定義的組件中,必須寫爲:


瞭解前端開發和JavaScript的同窗都知道,作事件綁定時咱們須要經過bind(或相似函數)來實現一個閉包以讓事件處理函數自帶上下文信息,這是由JavaScript語言特性決定的。而在0.13版本以前,React會自動在初始化時對組件的每個方法作一次這樣的綁定,相似於this.func = this.func.bind(this),這樣在JSX的事件綁定中就能夠直接寫爲onClick={this.handleSubmit}。

表面上看自動綁定給開發帶來了便利,而Facebook卻認爲這破壞了JavaScript的語言習慣,其背後的神奇(Magic)邏輯或許會給初學者帶來困惑,甚至開發者若是從React再轉到其它庫也可能會無所適從。基於一樣的理由,React還取消了對mixin的支持,基於ES6的React組件再也不可以以mixin的形式進行代碼複用或者擴展。儘管這帶來了很大不便,但Facebook認爲mixin增長了代碼的不可預測性,沒法直觀的去理解。關於mixin的思考,還能夠參考這篇文章。react-native

以簡單直觀、符合習慣的(idiomatic)方式去編程,讓代碼更容易被理解,從而易於維護和不斷演進。這正是React的設計哲學。數組

編寫可預測,符合習慣的代碼
所謂可預測(predictable),即容易理解的代碼。在年初的React開發者大會上,React項目經理Tom Occhino進一步闡述React誕生的初衷,在演講中提到,React最大的價值到底是什麼?是高性能虛擬DOM、服務器端Render、封裝過的事件機制、仍是完善的錯誤提示信息?儘管每一點都足以重要。但他指出,其實React最有價值的是聲明式的,直觀的編程方式。瀏覽器

軟件工程向來不提倡用高深莫測的技巧去編程,相反,如何寫出可理解可維護的代碼纔是質量和效率的關鍵。試想,一個月以後你回頭看你寫的代碼,是否一眼就明白某個變量,某個if判斷的含義;一個新加入的同事想去增長一個小小的新功能或是修復某個Bug,他是否對本身的代碼有足夠的信心不引入任何反作用?隨着功能的增長,代碼很容易變得愈來愈複雜,這些問題也將愈來愈嚴重,最終致使一份難以維護的代碼。而React號稱,新同事甚至在加入的第一天就能開始開發新功能。安全

那麼React是如何作的呢?

使用JSX直觀的定義用戶界面
JSX是React的核心組成部分,它使用XML標記的方式去直接聲明界面,界面組件之間能夠互相嵌套。可是JSX給人的第一印象倒是至關「醜陋」。當下面這樣的例子被第一次展現的時候,甚至不少人稱之爲「巨大的退步(Huge Step Backwards)」:

var React = require(‘React’);
var message =


Hello World
;
React.renderComponent(message, document.body);
將HTML直接嵌入到JavaScript代碼中看上去確實是一件足夠瘋狂的事情。人們花了多年時間總結出的界面和業務邏輯相互分離的「最佳實踐」就這麼被完全打破。那麼React爲什麼要如此另類?

模板出現的初衷是讓非開發人員也能對界面作必定的修改。但這個初衷在當前Web程序裏已徹底不適用,每一個模板背後的代碼邏輯嚴重依賴模板中的內容和DOM結構,二者是緊密耦合的。即便作到文件位置的分離,實際上二者仍是一體的,而且爲了二者之間的協做而不得不引入不少機制和概念。以Angularjs的首頁示例代碼爲例:

  • {{todo.text}}

儘管咱們很容易看懂這一小段模板的含義,但你卻沒法開始寫這樣的代碼,由於你須要學習這一整套語法。好比說,你得知道有ng-repeat這樣的標記的準確含義,其中的」todo in todoList.todos」看上去是repeat語法的一部分,或許還有其它語法存在;能夠看到有{{todo.text}}這樣的數據綁定,那麼若是要對這段文本格式化(加一個formatter)該怎麼作;另外,ng-model背後又須要什麼樣的數據結構?

如今來看React怎麼寫這段邏輯:

//...
render: function () {
var lis = this.todoList.todos.map(function (todo) {
return (



  • {todo.text}
  • );
    });
    return (

      {lis}

    );
    }
    //...
    能夠看到,JSX中除了另類的HTML標記以外,並無引入其它任何新的概念(事實上HTML標記也能夠徹底用JavaScript去寫)。Angular中的repeat在這裏被一個簡單的數組方法map所替代。在這裏你能夠利用熟悉的JavaScript語法去定義界面,在你的思惟過程當中其實已經不須要存在模板的概念,須要考慮的僅僅是如何用代碼構建整個界面。這種天然而直觀的方式直接下降了React的學習門檻而且讓代碼更容易理解。

    簡化的組件模型:所謂組件,其實就是狀態機器
    組件並非一個新的概念,它意味着某個獨立功能或界面的封裝,達到複用、或是業務邏輯分離的目的。而React卻這樣理解界面組件:

    所謂組件,就是狀態機器

    React將用戶界面看作簡單的狀態機器。當組件處於某個狀態時,那麼就輸出這個狀態對應的界面。經過這種方式,就很容易去保證界面的一致性。

    在React中,你簡單的去更新某個組件的狀態,而後輸出基於新狀態的整個界面。React負責以最高效的方式去比較兩個界面並更新DOM樹。

    這種組件模型簡化了咱們思考的方式:對組件的管理就是對狀態的管理。不一樣於其它框架模型,React組件不多須要暴露組件方法和外部交互。例如,某個組件有隻讀和編輯兩個狀態。通常的思路多是提供beginEditing()和endEditing()這樣的方法來實現切換;而在React中,須要作的是setState({editing: true/false})。在組件的輸出邏輯中負責正確展示當前狀態。這種方式,你不須要考慮beginEditing和endEditing中應該怎樣更新UI,而只須要考慮在某個狀態下,UI是怎樣的。顯而後者更加天然和直觀。

    組件是React中構建用戶界面的基本單位。它們和外界的交互除了狀態(state)以外,還有就是屬性(props)。事實上,狀態更多的是一個組件內部去本身維護,而屬性則由外部在初始化這個組件時傳遞進來(通常是組件須要管理的數據)。React認爲屬性應該是隻讀的,一旦賦值過去後就不該該變化。關於狀態和屬性的使用在後續文章中還會深刻探討。

    每一次界面變化都是總體刷新
    數據模型驅動UI界面的兩層編程模型從概念角度看上去是直觀的,而在實際開發中卻困難重重。一個數據模型的變化可能致使分散在界面多個角落的UI同時發生變化。界面越複雜,這種數據和界面的一致性越難維護。在Facebook內部他們稱之爲「Cascading Updates」,即層疊式更新,意味着UI界面之間會有一種互相依賴的關係。開發者爲了維護這種依賴更新,有時不得不觸發大範圍的界面刷新,而其中不少並不真的須要。React的初衷之一就是,既然總體刷新必定能解決層疊更新的問題,那咱們爲何不索性就每次都這麼作呢?讓框架自身去解決哪些局部UI須要更新的問題。這聽上去很是有挑戰,但React卻作到了,實現途徑就是經過虛擬DOM(Virtual DOM)。

    關於虛擬DOM的原理我在去年末的文章有過比較詳細的介紹,這裏再也不重複。簡而言之就是,UI界面是一棵DOM樹,對應的咱們建立一個全局惟一的數據模型,每次數據模型有任何變化,都將整個數據模型應用到UI DOM樹上,由React來負責去更新須要更新的界面部分。事實證實,這種方式不但簡化了開發邏輯而且極大的提升了性能。

    以這種思路出發,咱們在考慮不斷變化的UI界面時,僅僅須要總體考慮UI的構成。編程模型的簡化帶來的是代碼的精簡和易於理解,也即React不斷提到的可預測(Predictable)的代碼,代碼的功能一目瞭然易於理解。Tom Occhino在2015 React開發者大會上也分享了React在Facebook內部的應用案例,隨着新功能被不斷的添加到系統中,開發進度非但沒有變慢,甚至愈來愈快。

    單向數據流動:Flux
    既然已經有了組件機制去定義界面,那麼還須要必定的機制來定義組件之間,以及組件和數據模型之間如何通訊。爲此,Facebook提出了Flux框架用於管理數據流。Flux是一個至關寬鬆的概念框架,一樣符合React簡單直觀的原則。不一樣於其它大多數MVC框架的雙向數據綁定,Flux提倡的是單向數據流動,即永遠只有從模型到視圖的數據流動。

    Flux引入了Dispatcher和Action的概念:Dispatcher是一個全局的分發器負責接收Action,而Store能夠在Dispatcher上監聽到Action並作出相應的操做。簡單的理解能夠認爲相似於全局的消息發佈訂閱模型。Action能夠來自於用戶的某個界面操做,好比點擊提交按鈕;也能夠來自服務器端的某個數據更新。當數據模型發生變化時,就觸發刷新整個界面。

    Flux的定義很是寬鬆,除了Facebook本身的實現以外,社區中還出現了不少Flux的不一樣實現,各有特色,比較流行的包括Flexible, Reflux, Flummox等等。

    讓數據模型也變簡單:Immutability
    Immutability含義是隻讀數據,React提倡使用只讀數據來創建數據模型。這又是一個聽上去至關瘋狂的機制:全部數據都是隻讀的,若是須要修改它,那麼你只能產生一份包含新的修改的數據。假設有以下數據:

    var employee = {
    name: ‘John’,
    age: 28
    };
    若是要修改年齡,那麼你須要產生一份新的數據:

    var updated = {
    name: employee.name,
    age: 29
    };
    這樣,原來的employee對象並無發生任何變化,相反,產生了一個新的updated對象,體現了年齡發生了變化。這時候須要把新的updated對象應用到界面組件上來進行界面的更新。

    只讀數據並非Facebook的全新發明,而是起源於Clojure, Scala, Haskell等函數式編程語言。只讀的數據可讓代碼更加的安全和易於維護,你再也不須要擔憂數據在某個角落被某段神奇的代碼所修改;也就沒必要再爲了找到修改的地方而苦苦調試。而結合React,只讀數據可以讓React的組件僅僅經過比較對象引用是否相等來決定自身是否要從新Render。這在複雜的界面上能夠極大的提升性能。

    針對只讀數據,Facebook開發了一整套框架immutable.js,將只讀數據的概念引入JavaScript,而且在github開源。若是不但願一開始就引入這樣一個較大的框架,React還提供了一個工具類插件,幫助管理和操做只讀數據:React.addons.update。

    React思想的衍生:React Native, React Canvas等等
    在前幾天的Facebook F8開發者大會上,React Native終於衆望所歸的發佈,它將React的思想延伸到了原生移動開發。它的口號是「Learn Once, Write Anywhere」,有React開發經驗的開發人員將能夠無縫的進行React Native開發。不管是組件化的思想,調試工具,動態代碼加載等React具備的強大特性均可以應用在React Native。相信這會對之後的移動開發佈局產生重要影響。

    React對UI層進行了完美的抽象,寫Web界面時甚至可以作到徹底的去DOM化:開發者能夠無需進行任何DOM操做。所以,這也讓對UI層進行總體替換成爲了可能。React Native正是將瀏覽器基於DOM的UI層換成了iOS或者Android的原生控件。而Flipboard則將UI層換成了Canvas。

    React Canvas是Flipboard出品的一套前端框架,全部的界面元素都經過Canvas來繪製,infoQ以前也有文章對其進行了介紹。Flipboard追求極致的性能和用戶體驗,所以對瀏覽器的緩慢DOM操做深惡痛絕,不惜大刀闊斧完全捨棄了DOM,而徹底用Canvas實現了整套UI控件。有興趣的同窗不妨一試。

    小結
    React並非忽然從哪裏蹦出來,而是爲了解決前端開發中的痛點而生。以簡單爲原則設計也決定了React具備極其平緩的學習曲線,開發者能夠快速上手並應用到實際項目中。本文總結分析了其相關技術背後的設計思想,但願經過這個角度能讓你們對React有一個整體的認識,從而在React的實際項目開發中,遵循簡單直觀的原則,進行高效率高質量的產品開發。

    參考資料
    React官方網站:http://facebook.github.io/react/
    React博客:http://facebook.github.io/react/blog/
    React入門:http://ryanclark.me/getting-started-with-react/
    顛覆式前端UI框架:React:http://www.infoq.com/cn/articles/subversion-front-end-ui-development-framework-react
    Immutable.js: http://facebook.github.io/immutable-js/
    React Native: http://facebook.github.io/react-native/
    Flux: https://facebook.github.io/flux/
    Flux框架對比:https://github.com/voronianski/flux-comparison
    React開發者大會網站:http://conf.reactjs.com/index.html
    React在Slack上的聊天社區:http://reactiflux.com/

    轉自:http://www.infoq.com/cn/articles/react-art-of-simplity/

    相關文章
    相關標籤/搜索