React.js在Codecademy中的實際應用
2014年八月,Codecademy為了更新用戶的學習體驗,決定采用非死book的React.js庫,這是一個用于編寫 JavaScript UI的類庫。在開始階段,我們的問題是很難找到React在復雜應用中應用的示例,例如像Codecademy這樣復雜的應用。因為大多數的教程都是針對 小型示例應用上的特性,而不是針對在開發大型應用中更常見的問題。在本文中,我不僅會對React的使用進行一番概述,還會特別說明在大型web應用程序 中使用React的某些特別注意事項。
React是什么?
簡單來說:React是一個使用JavaScript創建用戶界面的代碼庫。與編寫用戶界面常見的方式不同,React將每個UI元素視為一個抑制 的狀態機。它并不是類似于AngularJS這樣的“框架”。雖然非死book有時會將React描述為“MVC中的V”,但我發覺這一描述并沒有什 么幫助,因為React應用并不需要遵守MVC模型。React能夠幫助你創建快速的用戶界面,處理復雜的交互,而無需編寫大量糟糕的代碼。
如果你打算在工作中使用React,你需要了解以下特性:
- React會為你處理DOM。 </ul>
- React使用聲明式風格以及組件。 </ul>
- React將標記緊密地結合在JavaScript中。 </ul>
- 單向信息流動。 </ul>
- 只有在用戶已登錄的前提下,才會顯示“Report a Problem”按鈕
- 只有在該練習中包括測試的情況下,才會顯示Instructions部分 </ul>
- LearningEnvironment
- CodeEditor
- RunButton
- ErrorDisplayer
- Navigation </ul>
- 當用戶單擊RunButton之后,該組件會在事件的調用中,通過回調方式通知它的父組件CodeEditor。
- CodeEditor組件會通過另一個回調函數通知它的父組件,即Learning Environment組件,并將用戶的當前代碼傳遞給父組件。
- LearningEnvironment組件將針對用戶的代碼進行測試。 </ul>
- LearningEnvironment組件會對CodeEditor中的屬性errorMessage賦值,CodeEditor則會依次為它的子組件ErrorDisplayer中的屬性errorMessage賦值。
- 如果用戶已經完成了該練習的所有測試,LearningEnvironment組件就會為Navigation組件中的屬性progress賦值。 </ul>
DOM操作的開銷很大,而React的吸引力很大程度上來自于它對這一問題的處理方式。React通過對自身虛擬DOM的維護,只在需要時進行重新渲染,將DOM操作的數量降至了最低,這要歸功于React中高性能的比較操作的實現。
這就意味著你很少會需要直接與DOM打交道,與之相反,React會替你處理DOM的操作。這一特性也是諸多React設計的基礎。如果你打算濫用React的API,或是打算按照自己的方式進行改動,那有可能會影響到React對DOM的理解。
該特性也使得使用Node.js進行內置的服務端渲染成為可能,這一點就使你能夠輕易地創建對于SEO友好的頁面。
在React中,所有的組件都必須繼承自Component類。組件中包含了屬性(由父類決定)和狀態(能夠自行改變,通常是基于用戶行為進行改變),組件的渲染和行為應當完全由它們的狀態和屬性所決定(而不依賴于任何其它值),因此組件就是狀態機。這一模型鼓勵使用者創建模塊化的UI,并且在實踐中能夠簡化UI的操作與創建工作。
雖然在JavaScript中編寫HTML代碼聽起來很奇怪,但在React中這是一種自然的選擇。JSX是原生的JS與HTML標記相結合的一種 語言,對于它的使用是可選的,但我強烈建議你選擇這種方式。React認為,由于你的標記已經緊密地結合在控制這些標記的JavaScript中,因此可 以將它們安置在同一個文件中,我也同意這一看法。
這一點更多的是一種通用的React模式,而不是一種嚴格的規則。信息的流動在React中傾向于單向流動。在本文的稍后部分中,當我們開始考慮在大型應用程序中如何處理信息流動時,會再次提及這一模式。
解析React應用程序
為了讓這些原則顯得更為清晰,讓我們看看Codecademy的學習環境是如何使用React進行構建的。
(單擊圖片以放大)
圖1:學習環境。
正如你在這個屏幕截圖中所看到的一樣,主要的學習環境由多個不同的UI元素所組成。某些元素是始終展現在頁面上的,例如header、menu和 navigation。而有些組件會根據當前的練習的不同,處于顯示或是不顯示的狀態。比方如,根據課程的不同,web瀏覽器、命令行和代碼編輯器可能會 進行混合或是匹配。
創建該界面的邏輯解決方案是為各個部分創建React組件。在下面的屏幕截圖中,我將特別指出主要的React組件:
(單擊圖片以放大)
圖2:學習環境以及對應的組件。
每個組件都有可能包含多個子組件:比方如,屏幕左方的Lesson面板實際上就包含了多個組件:
(單擊圖片以放大)
圖3:組成Lesson組件的各個子組件。
在這個示例中,我們將使用React以決定哪些組件應該顯示在該lesson面板中。舉例來說:
此外,React會處理該組件與其它組件之間的信息流動。整個學習環境對應著一個父組件,該組件會持續跟蹤多個狀態,例如當前用戶處于哪個練習中。父組件會為子組件的屬性進行相應的賦值,以決定這些子組件如何顯示。
現在,讓我們來看一個組件通信的示例,以下組件來自經過大量簡化后的組件樹結構:
(單擊圖片以放大)
圖4:與代碼提交相關的某些組件。
如果某個用戶打算運行他的代碼,我們該如何處理這一工作流?我們將嘗試對他們提交的代碼進行測試,然后顯示錯誤信息,或是允許用戶繼續下一題。以下是某個可能發生的工作流:
根據結果的不同……
如果我們的組件都能夠像在LearningEnvironment組件中的render方法一樣進行聲明(同樣進行了大量簡化),那么就可以通過一個單一的函數調用實現整個UI的更新:
render: function() { return( <div> <CodeEditor error={this.state.error} /> <Navigation mayProceed={this.state.mayProceed} /> </div>); }
請記住,React中混合了JavaScript和HTML標記。在這個例子中,render方法定義了一個LearningEnvironment組件,其中包括了一個CodeEditor組件和一個Navigation組件。
我們可以更新LearningEnvironment組件的狀態,它會觸發組件的重繪,并在必要時更新子組件。
handleTestResult: function(currentIndex, passing, error) { this.setState({ error: error, mayProceed: passing && currentIndex === this.state.currentExercise.tests.length-1 }); }
這就是全部的代碼了。React以一種優雅而簡單的方式替我們處理UI的更新操作。
大型應用程序中的考慮因素
信息流動
正如我之前所說的一樣,React不一定要遵循MVC模型,實際上,你可以按照任何你喜歡的方式處理信息流動,但你需要一種確切的信息策略。為了讓 屬性的變化傳遞給子-子-子-子-子組件,你是否需要將該屬性一路傳遞下去,哪怕中間的那些子組件完全不需要了解該屬性?如果該葉子節點接受用戶輸入,它 又該如何將這一變更通知它的父-父-父-父組件呢?
在大型應用程序中,這種處理方式是很令人受挫的。即使是在以上那個簡單的示例中,Run按鈕該如何與LearningEnvironment組件之間傳遞用戶的行為呢?我們需要傳遞回調函數,但這種方式很難寫出真正模塊化、可重用的組件。
Codecademy的解決方案是通過創建通信適配器(Adapter),以管理各別組件間的信息流動。與傳遞回調函數的方式不同,高層次的組件, 例如CodeEditor會接收到一個Adapter,它為重要的通信任務提供了一種單一的接口。舉例來說,當CodeEditor處于顯示狀態 時,LearningEnvironment會創建一個Adapter,它能夠生成和處理與用戶提交代碼相關的事件。
這種方式也不是完全沒有缺陷的,我也在React大會的演講中針對這一點進行了詳細的論述。我的主要觀點在于,無論你如何處理組件樹中的信息流動,你的團隊都應該堅持一種一致的策略。
整合
React的上手非常簡單,但要在你的工作流中高效地使用它,你需要一些工具的支持。舉例來說,我們使用了以下工具:
- 用一段腳本對.jsx文件的本地文化進行監控,并在必要時對它們進行重新編譯
- 一個獨立的node.js服務器,用于處理服務端的展示
- 用于在需要時自動生成新組件文件的開發者工具 </ul>
以上這些工具都不是非常復雜。對于.jsx的監控來說,Gulp是一個很好的選擇,不過我們選擇了使用Go語言自行編寫腳本。我們使用了一個簡單的 批處理腳本負責生成新的組件文件,這種方式也能夠確保命名規范。如果你打算使用一個node.js服務器以負責服務端展示,你需要當心的是,要強制 require.js能夠獲取到React代碼中的變更可能會有些困難,因此我們創建了一個監控器,讓它在必要時重啟node服務器。
為什么使用React?
在我們重新設計整個學習環境時,我們需要決定選擇使用哪一套工具或框架。我們最終選擇了React,對這一決定我們感到非常滿意。(關于我們如何選擇一套JavaScript框架的詳細過程,可以在以下演講視頻中找到:https://www.油Tube.com/watch?v=U5yjPG5mHZ8)
我們對于React的欣賞之處主要在于以下幾個方面:
它經過了實戰檢驗
React已經在非死book和Instagram的生產環境中得到應用,因此我們對于它的性能和可靠性很有信心。目前為止,它在我們的平臺上同樣表現良好,我們也沒有遇到過任何嚴重的問題。
組件化的方式便于理解。
React對每個獨立的組件進行單獨處理,這些組件會按照它內部的狀態進行展現,因此對于某一時刻應該發生什么事,很容易形成概念化的理解。你的應 用程序會有效地成為一個大型狀態機。這意味著你可以單獨測試UI中的每個片段,同樣可以自由地添加新組件,而無需擔心會影響整個應用程序中其它部分的代 碼。
SEO非常容易實現。
因為React本身就支持服務端展現,因此在搜索引擎看來,你提供的是一個基本已完成的頁面,這對于SEO來說是一個極大的優勢,而所需的工作量非 常小。的確,這一點必需由Node完成。由于Codecademy的主應用是由Rails編寫的,因此我們搭建了一個獨立的Node服務器,專門用于處理 React的展現。
React能夠兼容遺留代碼,并且它的靈活性足以應對未來。
雖然采用一整套框架的確是一件大事,但你也可以慢慢地嘗試將React添加到現有的代碼庫中。與之類似,如果將來我們需要移除React,我們也可 以輕易地實現這一點。在Codecademy,我們首先決定完全使用React來編寫一個全新的項目,以便嘗試它的功能,并學習如何以最佳的方式使用它。 這個項目很成功,因此我們現在基本上在所有的新UI元素中都使用React了。我建議你首先做些功課,創建一些實驗項目,然后再考慮怎樣讓React適應 于你的現有代碼庫。
不必擔心編寫樣板代碼了。
在編寫樣板代碼上所花的時間越少,就意味著你可以將更多的時間花在更有意義的問題上了。從這個角度上來說,React是個既簡潔又輕量級的類庫。以下代碼是創建一個新的組件所需的最少代碼:
var dummyComponent = React.createClass({ render: function() { return (<div>HTML markup in JS, what fun!</div>); } });
簡短且切題,還有什么不滿意的?
我們的社區正在成長
React社區的發展非常迅速。當你遇到各種問題時,你可以和許多社區成員討論這一問題。并且,由于許多公司都已經在生產環境中使用了React(僅舉幾例,非死book、Instagram、Yahoo!、Github和Netflix),因此我們并不獨孤。
總結
React是一個輕量級、強大,并且經過實戰檢驗的使用JavaScript創建用戶界面的類庫。它不是一個框架,而是一個強大的工具,或許會改變 你進行前端開發的方式。我們認為它對于我們的前端開發來說,作用之大是難以置信的,而我們對于自己的選擇也感到相當滿意。對我自己來說,使用React進 行工作至少是極大地影響了我思考編寫用戶界面的方式。我也樂于看到React的不斷成長:現在非死book已經通過React Native將React的功能帶到移動開發上了,我想它的未來一定會是一片光明。
如果你打算上手使用React,它的教程是一個不錯的邏輯起點。互聯網上也有著大量介紹React中的關鍵概念的帖子(這個幻燈片是我最愛的教程之一)。不要停下腳步,學習鉆研,嘗試著創建些什么,然后看看你對于React這種前端開發方式是怎么想的。我非常樂于聆聽你的想法,請將你的想法發送至我的推ter帳號@brindelle。
關于作者
Bonnie Eisenman是一位來自于Codecademy.com的軟件工程師。她最近剛剛從普林斯頓大學的計算機科學專業畢業。她對硬件也有一定興趣,在業余時間喜歡從事一些Arduino方面的工作,以及樂曲編輯。她的推ter帳號是@brindelle。
自從HTML5變得流行以來,整個Web平臺取得了長足的進步,人們也開始將JavaScript視為一門能夠創建復雜應用的語言。許多新的API紛紛浮現,而關于瀏覽器如何應用這些技術的文章也大量涌現。
這一系列文章的視角更進一步,它們將關注于如何在實踐中應用這些強大的技術,這并不是指創建多么酷炫的示例和原型,而是在第一線進行實際應用。在這個(后)HTML5系列文章中,我們不需要響亮的口號,而是基于行業專家的實際經驗,獲得實踐性的見解。我們也將討論那些更進一步的技術(例如AngularJS),并對web標準和web開發的未來進行定義。
查看英文原文:React.js in Real Life at Codecademy
來自:http://www.infoq.com/cn/articles/reactjs-codecademy