教育行業(yè)A股IPO第一股(股票代碼 003032)

全國咨詢/投訴熱線:400-618-4000

不僅用于UI構(gòu)建:Facebook React完全解析

更新時間:2015年12月29日15時01分 來源:傳智播客前端與移動開發(fā)學(xué)科 瀏覽次數(shù):

原文鏈接 http://www.csdn.net/article/2015-08-04/2825370-react
 
2004年,對于前端社區(qū)來說,是里程碑式的一年。Gmail橫空出世,它帶來基于前端渲染的原生應(yīng)用級別的體驗,相對于之前的服務(wù)端渲染網(wǎng)頁可謂提升了一個時代,觸動了用戶的G點。自此,前端渲染的網(wǎng)站成為無數(shù)開發(fā)者追逐的方向。
為了更好地開發(fā)前端渲染的“原生級別的”網(wǎng)站,包括Backbone和Angular在內(nèi)的一系列前端框架應(yīng)運而生,并迅速獲得了大規(guī)模的采用。但 是很快地,新的性能和SEO問題也接踵而來。幾經(jīng)嘗試后,Twitter甚至從前端渲染重回服務(wù)器渲染,而Strikingly也面對過同樣棘手的問題。
2014年,React進入我們的視線。讓人耳目一新的是,對于其他開源框架遇到的種種問題,React都自信地給出了解答。幾乎沒有猶豫,我們開始使用React來重構(gòu)Strikingly。若干年后,當我們回望,也許會發(fā)現(xiàn),2014年也是前端社區(qū)里程碑式的一年。
React簡介 
React究竟是什么?Facebook把它簡單低調(diào)地定義成一個“用來構(gòu)建UI的JavaScript庫”。這個定義也許會讓我們聯(lián)想到許多 JavaScript模板語言(比如Handlebars和Swig),或者早期的控件庫(比如YUI和Dojo),但是React所基于的幾個核心概念 使它與那些模板和控件庫迥然不同。事實上這幾個核心概念非常超前,已經(jīng)給整個前端世界帶來了沖擊性的影響。它們包括:
1. 組件和基于組件的設(shè)計流程; 
2. 單向數(shù)據(jù)流動; 
3. 虛擬DOM取代物理DOM作為操作對象; 
4. 用JSX語法取代HTML模板,在JavaScript里聲明式地描述UI。 
這幾條簡單的原則放在一起帶來了大量的好處:
1. 前端和后端都能夠從React組件渲染頁面,完全解決了SEO長期困擾JavaScript單頁應(yīng)用的問題; 
2. 我們可以簡單直接地寫前端測試而完全忘掉DOM依賴; 
3. 組件的封裝方式和單向數(shù)據(jù)流動能夠極大地簡化前端架構(gòu)的理解難度。 
我們來看一個例子:
[js] view plaincopy
1. var HelloMessage = React.createClass({   
2.     render: function() {  
3.         return <div>Hello {this.props.name}</div>;    
4.     }  
5. });  
6.   
7. React.render(<HelloMessage name="John" />, document.body);  
這個React版的Hello World已經(jīng)展現(xiàn)了React的一些核心特性。首先,HelloMessage是一個React組件;創(chuàng)建React應(yīng)用的時候,我們總是以組件為出發(fā) 點。每個組件的核心是一個render方法,在其中我們把這個組件的props和state拼裝到一個最終要渲染的模板中,然后返回這個模板(確切地說這 里是一個UI描述而不是傳統(tǒng)意義上的模板)。這段代碼里看起來像HTML一樣的部分就是著名的JSX語法,它是在React中描述“模板”的最佳方式。
現(xiàn)在,以var開頭的第一段里我們定義了一個叫HelloMessage的組件;下面的React.render這一行所做的,則是把這個組件渲染 到document.body里——也就是我們實際的頁面上。但在使用〈HelloMessage/〉的時候,我們做了另一件 事:name="John"??雌饋砗芟馠TML中的元素屬性,但是既然JSX不是HTML,這個語法的作用是什么呢?實際上,這就是我們向React組 件傳入props的方式。回頭看第一段,我們可以看到在組件的內(nèi)部有對this.props.name的引用。這個name就是我們剛剛指定的John!
看到這里,如果你熟悉jQuery的話也許在想,這與$(document.body).html('Hello John') 有什么根本區(qū)別呢?
這就是虛擬DOM出場的地方了。我們像寫HTML一樣寫JSX,但是JSX并不會直接變成HTML和DOM。在幕后,React維護著一個虛擬 DOM,而實際上被瀏覽器直接操作的“物理”DOM只是這個虛擬DOM的投影。虛擬DOM不依賴于瀏覽器環(huán)境,它可以運行在任何JavaScript執(zhí)行 環(huán)境。這就讓下面的代碼成為可能:
[js] view plaincopy
1. var html =React.renderToString(<HelloMessage name="John"/>);res.send(html);  
如果第二行有點眼熟,你沒有猜錯——這段代碼發(fā)生在服務(wù)器端!是的,同樣的 HelloMessage,我們不僅可以讓React在前端渲染到頁面,同樣可以在后端直接渲染成HTML字符串,然后把它返回給前端。服務(wù)端預(yù)渲染就這么自然地發(fā)生了。
React帶來的革命性創(chuàng)新是前端世界過去幾年最激動人心的變化。自從接觸React以來,我們深信React會徹底改變客戶端開發(fā)者(包括前端、 iOS和Android)的開發(fā)體驗。在下面的篇幅里,我們想從四個大的方向——目標平臺(Targets)、數(shù)據(jù)處理(Data)、工具(Tools) 和新的挑戰(zhàn)——分享一下React生態(tài)系統(tǒng)和社區(qū)的進展和未來趨勢。
目標平臺 
對于虛擬DOM的討論,很多人會說速度快過于真正的DOM。這樣的討論可以讓人快速入門理解React,但是真正寫過React應(yīng)用的人會明白速度 并不是虛擬DOM的精髓。我們認為虛擬DOM的存在幫助我們做到了兩件事。第一是申明式UI。通過虛擬DOM,UI不再是一個不斷被更變的DOM,你只要 申明UI是怎么生成的,React會自動幫你把UI的改變渲染到真正的DOM上。這種新的思維方式讓你可以不用手動操作真正的DOM。第二是多 Target。我們一直在講Web,但React讓我們做到Web以外的Target。虛擬DOM更像是UI虛擬機,自動幫你映射到真正的實現(xiàn)上,可以是 瀏覽器DOM、iOS UI、Android UI。甚至有人做到了React映射到終端文本UI。
多Targets是React社區(qū)常常在討論的主要話題之一。多Targets的根本是提高開發(fā)者體驗。開發(fā)者體驗(DX,Developer Experience)是在React社區(qū)里屢次被提起的概念。如何在保持一樣的用戶體驗下,提高開發(fā)者體驗,是包括React在內(nèi)的前端社區(qū)正在思考的 問題。事實上任何一家有多客戶端的公司都面臨著這樣同一個問題:在各種客戶端語言里重新造輪子。開發(fā)者需要學(xué)習(xí)新的語言、寫和維護類似的功能。提升客戶端 開發(fā)者體驗就是減少學(xué)習(xí)成本和維護成本。這就是React提倡的“Learn once,write everywhere”。
最近也有一些鼓舞人心的消息。Facebook內(nèi)部Ads Manager iOS版本由7位前端工程師用React Native花了5個月完成。而Android版本,是同一班人,3個月內(nèi)完成。代碼重用率達到了87%。
多Targets也可以是在單個平臺更深度的結(jié)合。來自React核心團隊的Sebastian Markbåge在ReactEurope大會上給了一個讓人目瞪口呆的演講《DOM as a Second-class Citizen》。演講中他暢想React直接輸出到瀏覽器架構(gòu)的底層(圖1瀏覽器的渲染架構(gòu),圖2為Sebastian Markbåge認為React可以做的事情)。
圖1 瀏覽器的渲染架構(gòu) 
圖2 Sebastian Markbåge認為React還可以做很多事情 
姑且不談該不該這么做,通過虛擬DOM打開了這樣的機會就已經(jīng)讓我們興奮不已了。也說明了Facebook在設(shè)計React時已經(jīng)考慮到超越DOM。想法確實很超前。
【服務(wù)端預(yù)渲染(Pre-rendering)】
對于其他主流前端框架,頁面SEO和首次打開速度的問題都很讓人頭疼。Twitter當年因為首次打開速度過于慢甚至重回服務(wù)器渲染方案。一直以來 人們一直在尋找一種只需要編寫一次UI組件,前后端同時都能渲染的方案。如果能做到的話,我們就可以在首次打開頁面時先用服務(wù)端渲染頁面HTML,當瀏覽 器收到后已經(jīng)可以顯示頁面。這樣SEO和首次打開速度都能被解決。這種完美方案社區(qū)里稱之為Isomorphic/Universal App。
React原生支持了Pre-rendering(服務(wù)端渲染)。由于有虛擬DOM,也就意味著我們只需要后端運行JavaScript引擎就能渲染整個DOM。目前主流后端語言都可以運行V8 JavaScript引擎。比如Strikingly的后端使用Ruby on Rails,只需要使用開源的react-rails gem就可以在Rails后端渲染前端React組件。
使用服務(wù)端渲染時要注意window和document這些瀏覽器才有的全局變量是不存在的。React組件提供這兩個lifecycle hook:componentDidMount和componentDidUpdate在服務(wù)器不會被運行,只有在前端才會運行。使用服務(wù)器渲染時如果要 使用任何瀏覽器才有的變量需要把代碼放到這兩個lifecycle hook定義里。
數(shù)據(jù)處理 
React定義自己為MVC中的View。這讓前端開發(fā)者從V開始去思考UI設(shè)計。但現(xiàn)在針對數(shù)據(jù)操作和獲取方式,社區(qū)里還沒有一種公認的方法。這也是任何寫React應(yīng)用時最難處理的地方。
【Flux】
對于M和C,F(xiàn)acebook提出了Flux的概念。Flux是一個專門為React設(shè)計的應(yīng)用程序架構(gòu):應(yīng)用程序由Dispatcher、Store和View組成,其中的View就是我們的React組件。Flux的核心是如圖3所示的單向數(shù)據(jù)流動。
圖3 單向數(shù)據(jù)流動為Flux的核心 
應(yīng)用程序中的任何一次數(shù)據(jù)變化都作為Action發(fā)起,經(jīng)過Dispatcher分發(fā)出去,被相關(guān)的Store接收到并整合,然后作為props和 state提供給View(React組件)。當用戶在View上做了任何與數(shù)據(jù)相關(guān)的交互,View會發(fā)起新的Action,開啟一次新的數(shù)據(jù)變化周 期。這種單向性使Flux在高層次上比傳統(tǒng)MVC架構(gòu)和以Angular和Knockout為代表的雙向數(shù)據(jù)綁定容易理解得多,大大簡化了開發(fā)者的思考和 Debug過程。
在Facebook把Flux作為一種設(shè)計模式(而不是已經(jīng)做好的框架)宣布之后,幾乎每個月出現(xiàn)一新的Flux庫,他們都有各自的特色,有的對服 務(wù)器渲染支持比較好,有的運用了更多函數(shù)式編程的概念。很多Flux庫更像是實驗,這有助于React生態(tài)的生長,但不可否認的是,未來會有大量Flux 庫慢慢死去,而只有少數(shù)會存留下來或進行合并。
【GraphQL】
在構(gòu)建大型前端應(yīng)用時,前端和后端工程師通過API的方式進行合作。API也是雙方的協(xié)議?,F(xiàn)在主流的方式是RESTful API,然而在實踐中,我們發(fā)現(xiàn)RESTful在一些真實生產(chǎn)環(huán)境的需求下不是很適用。往往我們需要構(gòu)建自定義endpoint,而這違背了 RESTful的設(shè)計理念。
舉個例子,我們想要顯示論壇帖子、作者和對應(yīng)的留言。我們分別要發(fā)出三個不同的請求。第二個請求依賴第一個請求結(jié)果返回的user_id,前端需要寫代碼協(xié)調(diào)請求之間的依賴。分別發(fā)出三個不同請求在移動端這種網(wǎng)絡(luò)不穩(wěn)定的環(huán)境下效果很不理想。
[js] view plaincopy
1. GET /v1/posts/1  
2.  {   
3.       "id": 1,   
4.       "title":"React.js in Strikingly",  
5.       "user_id":2  
6.   }  
[js] view plaincopy
1. GET /v1/users/2  
2.  {   
3.       "id":2,  
4.       "name":"dfguo"  
5.   }  
[js] view plaincopy
1. GET /v1/posts/1/comments  
2.   [{  
3.       "id":6,  
4.       "name":"rechtar",  
5.       "comment":"Thanks for sharing! I would love to see some examples on GraphQL."},{  
6.       "id":9,  
7.       "name":"tengbao",  
8.       "comment":"I heard that you guys also use immutable.js. How did it help?"},{  
9.       "id":12,  
10.       "name":"syjstc",  
11.       "comment":"Impressive work! Thanks guys!"  
12.   },{  
13.       "id":18,  
14.       "name":"abeth86",  
15.       "comment":"Thanks for the sharing!"  
16.   }]  
為解決這類問題,工程師會自定義一些endpoint。對于這個例子,我們可以建立一個/feeds的endpoint,集合了所有前端需要的結(jié)果:
[js] view plaincopy
1. GET /v1/feeds/1  
2.  {   
3.       "id":1,  
4.       "title":"React.js in Strikingly",  
5.       "user":{  
6.          "id":2,  
7.          "name":"dfguo"  
8.        },  
9.       "comments":[  
10.     {  
11.               "id":6,  
12.               "name":"rechtar",  
13.               "comment":"Thanks for sharing! I would love to see some examples on GraphQL."  
14.           }...  
15.    ]  
16.   }  
但是我們在某些場景上可能只需要post和user,不想要comments。這時難道要再定義一個feeds_without_comments 的endpoint?隨著需求的改變,自定義endpoint的方法往往使得API接口變得累贅,違背了RESTful的設(shè)計理念。而任何前端工程師需要 的數(shù)據(jù)一旦要改變都需要后端工程師的配合,這降低了產(chǎn)品的迭代速度。
來自Facebook的GraphQL是我認為目前最接近完美的解決方法。后端工程師只需要定義可以被查詢的Type System,前端工程師就可以使用GraphQL自定義查詢。GraphQL查詢語句只需要形容需要返回的數(shù)據(jù)形狀: 
{
       post(id:1){
            id,
            title,
            user{
                 id,
                 name
           },
          comments{
                id,
                name,
                comment
          }
     }
  }
GraphQL服務(wù)器就會返回正確的JSON格式:
[js] view plaincopy
1. {  
2.       "id":1,   
3.       "title":"React.js in Strikingly",   
4.       "user":{   
5.           "id":2,   
6.           "name":"dfguo"  
7.        },  
8.        "comments":[  
9.           {  
10.                "id":6,  
11.                "name":"rechtar",  
12.                "comment":"Thanks for sharing! I would love to see some examples on GraphQL.  
13.            }...  
14.        ]  
15.    }  
GraphQL也原生支持了API版本控制,讓你可以同時共存多個版本的客戶端(包括Web和Mobile)。這些都會減少客戶端工程師和后端工程師的耦合度,提高生產(chǎn)力。
今年7月剛推出了GraphQL的規(guī)范并開源了JavaScript GraphQL庫。然而要讓GraphQL成為主流,F(xiàn)acebook需要打造一個像React這樣的生態(tài)系統(tǒng)。要想在你自己的應(yīng)用上用GraphQL還 必須要有后端語言提供GraphQL庫的支持。比如Strikingly需要GraphQL Ruby庫。這不僅僅需要前端工程師。我們認為這將會比React生態(tài)系統(tǒng)更難建立(見圖4所示)。Facebook需要整個社區(qū)的參與才能達到。
圖4 GraphQL生態(tài)系統(tǒng) 
【Relay】
Relay是Facebook提出的在React上應(yīng)用GraphQL的方案。React的基礎(chǔ)單位是組件(Component),構(gòu)建大型應(yīng)用就 是組合和嵌套組件。以組件為單位的設(shè)計模式是目前社區(qū)里最認可的,這也是前端世界的趨勢之一。每個組件需要的數(shù)據(jù)也應(yīng)該在組件內(nèi)部定義。Relay讓組件 可以自定義其所需要GraphQL數(shù)據(jù)格式,在組件實例化的時候再去GraphQL服務(wù)器獲取數(shù)據(jù)。Relay也會自動構(gòu)建嵌套組件的GraphQL查 詢,這樣多個嵌套的組件只需要發(fā)一次請求。Relay將會在8月份開源。
【Immutability】
React社區(qū)接受了很多函數(shù)式編程的想法,其中受Clojure影響很深。對Immutable數(shù)據(jù)的使用就是來自Clojure社區(qū)。當年 Om,這個用ClojureScript寫的React wrapper在速度上居然完虐原生JavaScript版本的React。這讓整個社區(qū)都震驚了。其中一個原因就是ClojureScript使用了 Immutable數(shù)據(jù)。React社區(qū)里也冒出了Immutable.js,這讓JavaScript里也能使用Immutable數(shù)據(jù),完美彌補了 JavaScript在負責數(shù)據(jù)對象比較的先天性不足。Immutable.js也成為了構(gòu)建大型React應(yīng)用的必備。甚至有在討論是否把 Immutable.js直接納入JavaScript語言中。我們認為小型應(yīng)用不會遇到虛擬DOM的性能瓶頸,引入Immutable.js只會讓數(shù)據(jù) 操作很累贅。
工具 
工欲善其事,必先利其器。React的火爆得力于來自社區(qū)的工具,而React也推動了這些工具的進步。這里我們想介紹幾個React社區(qū)里比較受歡迎的工具。
【W(wǎng)ebpack】
在React里,由于需要用到JSX,使用Webpack或Browserify這類工具編譯代碼已經(jīng)漸漸成為前端工程師工作流程的一部分。Webpack是一款強大的前端模塊管理和打包工具(見圖5所示)。這里列出它的一些特性:
1. 同時支持CommonJS和AMD模塊;
2. 靈活和可擴展的Loader(加載器)機制,例如提供對JSX、ES6、Less的支持;
3. 支持對CSS,圖片等其他資源進行打包;
4. 可以基于配置和智能分析打包成多個文件;
5. 內(nèi)置強大的Code Splitting功能可以拆分并動態(tài)加載包;
6. 開發(fā)模式支持Hot Module Replacement模式,提高開發(fā)效率。
圖5 前端模塊管理和打包工具Webpack 
【Babel】 
ECMAScript 6(ES6)規(guī)范在今年四月剛敲定,React社區(qū)基本全面擁抱ES6。但目前還有很多瀏覽器不支持ES6。使用像Webpack這樣的工具編譯代碼使得 我們可以在開發(fā)時使用ES6(或者更新版本),在上線前編譯成ES5。編譯工具中最引人注意的是Babel。前身為ES6to5,Babel是目前社區(qū)最 火的ES6編譯到ES5的代碼工具,F(xiàn)acebook團隊甚至已經(jīng)決定轉(zhuǎn)用Babel而不再維護之前內(nèi)部使用的jstranform。通過Loader機 制,Webpack可以非常簡易地和Babel結(jié)合應(yīng)用。
【React-hot-reload】
在開發(fā)任何大型前端應(yīng)用過程中,我們常常會因為一些小錯誤就需要重新刷新整個頁面。React-hot-reload嘗試解決這個問題,提高開發(fā)效率。他使用了Webpack的Hot Module Replacement功能,動態(tài)替換React組件的lifecycle hook定義,不用刷新頁面也可以更新代碼變化。
【React Developer Tool】
這款Facebook官方推出的Chrome插件可以讓你方便地在瀏覽器中直接查看React的組件結(jié)構(gòu)。安裝后,在Chrome開發(fā)者工具中會多出一個React Tab。界面就像DOM Inspector一樣,只不過是看React組件結(jié)構(gòu)關(guān)系。是開發(fā)React應(yīng)用不可多得的工具之一。
挑戰(zhàn) 
React正在快速開拓著它的疆界,這意味在獲得新的喜悅的同時,我們也面臨著許多新的挑戰(zhàn)?,F(xiàn)在圍繞著幾個大的議題,React社區(qū)仍沒有達成定論,每周甚至每天都有新的實驗項目在嘗試這些問題的解決。
【動畫】
一直以來大家都對動畫應(yīng)該在React里怎么表達為狀態(tài)感到困惑。Cheng Lou的React Tween State是我們認為最符合React思維的做法。把位移存在State里,然后通過JavaScript動態(tài)渲染新的位置。不過大家對該做法是否能達到 滿意的速度一直持有保留態(tài)度。在今年ReactEurope的演講中,他為我們展現(xiàn)出了出色的效果和速度,非常值得一看。
在Strikingly,我們對于動畫則采取了比較實用主義的處理方式:我們定義了一些容器組件,比如〈JQFade/〉和〈JQSlide/〉, 在其中調(diào)用jQuery的動畫方法來實現(xiàn)相應(yīng)的Transition。這種方式在理論上并不完全符合React的精神,不過到現(xiàn)在為止還是能夠滿足我們需 求的。
【Flux庫與Relay】
正如上文已經(jīng)提到過的,目前Flux的各種實現(xiàn)可謂是百花齊放,其中還并沒有出現(xiàn)一個具有權(quán)威性的事實標準。Relay同樣也是剛剛孵化不久的新生概念——所有這些意味著雖然Flux+Relay會帶來生產(chǎn)力的飛升,要實際用上它們我們還要待以時日。
【CSS】
CSS是一個有趣的話題:似乎所有人都覺得當前的CSS有深刻的缺陷,但是對于怎么解決這些缺陷大家的意見卻分成了兩派各不相讓:一派認為CSS “可以被修好”,并且致力于修好它,由此誕生了cssnext這樣的項目;另一派認為CSS從根本上作為誕生于一個古老時代的東西,已經(jīng)不能適應(yīng)大規(guī)模、 組件化的現(xiàn)代開發(fā)流程,這一思想集中反映在Christopher Chedeau的演講《React: CSS in JS》中;在其中他提出了CSS的七個根本問題,然后指出在JavaScript中直接使用inline CSS可以幾乎“免費”地解決所有這些問題。在傳統(tǒng)的Web開發(fā)最佳實踐中inline CSS一直是被壓制的反面實踐,現(xiàn)在我們卻能夠以一個全新的視角看待它,這也完美地例證了React真的是在給整個前端世界帶來根本性的推動。
總結(jié) 
在不久前的JSConf 2015上赫門提出了前端的摩爾定理:前端每18月會難一倍。前端之所以變化這么快,是因為我們現(xiàn)在面臨著前所未有的工程化挑戰(zhàn)。今天的前端復(fù)雜度跟幾年 前完全不是一個等級。這也促使社區(qū)要找到在這種復(fù)雜度下能保持開發(fā)效率和開發(fā)體驗的工具和設(shè)計模式。React社區(qū)從其他領(lǐng)域(游戲渲染、 ClojureScript、函數(shù)式編程)偷師學(xué)藝,結(jié)合前端面臨的獨特問題,提出了一系列解決方案。React社區(qū)在各方面都推動著前端社區(qū)往前進。這 對整個社區(qū)都是好事。我們也希望前端各個框架可以互相學(xué)習(xí),共同推動整個社區(qū)的發(fā)展。
 
0 分享到:
和我們在線交談!