「您聲明性地指定 Web UI 組件分層結構,並將它提供給 React 的虛擬 DOM。而後 React 負責在合適的時機同步您的 UI 與瀏覽器的實際 DOM。」css
React 是做爲一個 JavaScript 庫提供的,其中包含一個 JSX 編譯器和關聯的開發人員工具。React 有助於建立可重用的高性能 UI 視圖組件,這些組件可組合在一塊兒,造成現代 Web UI。經過遵循規定的最佳實踐,您能夠一種可維護、可重用的方式設計這些組件。html
爲了優化運行時性能,React 組件首先呈現到一個託管的虛擬 DOM 中,如圖 1 所示。react
您聲明性地指定 Web UI 組件分層結構,並將它提供給 React 的虛擬 DOM。而後 React 負責在合適的時機同步您的 UI 與瀏覽器的實際 DOM。React 的虛擬 DOM 實現是自成一體的,不依賴於瀏覽器;它甚至可用於服務器端呈現(參見 參考資料)。虛擬 DOM 針對瀏覽器的 DOM 而對其內部狀態執行一次優化的 diff 指令,執行讓 UI 保持一致所需的最少更新。git
這種保留模式方法(參見 保留模式操做 邊欄)繞過了一大類與直接修改瀏覽器的 DOM 元素(一次一個可視過渡)相關的性能問題(就像在 jQuery UI 等流行的 UI 組件庫的操做中同樣)。github
不一樣於相似的庫,React 不會執行傳統的模型-視圖-控制器 (MVC) 風格的 UI 構造和狀態管理。相反,React 僅關注視圖的構造,該項目分享了一些有關爲何 MVC 可能不太適合複雜 Web UI 建立的觀點(參見 在複雜現代 UI 中使用 MVC 的誤區 邊欄)。可是,React 沒法阻止 MVC 結構的使用;許多開發人員最初的 React 經驗包括將新代碼集成到現有的基於 MVC 的大型項目的視圖層中。web
React 的一些使人喜好的特徵包括:gulp
JSX 是一個轉換器和編譯器,它接受一種擴展的 JavaScript 語法。JSX 可將如下這樣通過擴展的、相似 HTML 的表示法轉換爲:
<div> <MyLabel text={TextLabel} /> <MyTextfield /> <MyButton textlabel='OK' /> </div>
相似這樣的 JavaScript React API 調用:
React.createElement("div", null, React.createElement(MyLabel, {text: TextLabel}), React.createElement(MyTextfield, null), React.createElement(MyButton, {textlabel: "OK"}))
JSX 使人熟悉的、相似 HTML 的表示法大大簡化了 React 組件的編碼和維護。這種簡化對聲明深度嵌套的組件關係尤其明顯。
JSX 使您可以將 UI 邏輯與相關的結構描述一塊兒放在單個文件中,這有助於提升生產力並減小大型項目中的錯誤。對於其餘框架,要同步的文件數量多達此方法的 3 倍:模板文件、處理函數 JavaScript 代碼文件和結構化 HTML 描述。
JSX 可在瀏覽器中運行或獨立運行。瀏覽器中的轉換器可在開發上提供幫助,由於您在修改 JSX 代碼後可當即看到結果。對於生產環境,您必定但願將獨立的轉換器合併到您的構建工具鏈中,以便提供實現最佳的性能。
出於簡便性,本教程的全部示例(參見 下載)都使用瀏覽器內的轉換器。第一個示例 (example1) 從 CDN 得到 React 和 JSX 轉換器,是實現正常運行的最簡單方式(將文件放在 Web 服務器背後並訪問它們的 URL):
<html lang="en"> <head> <script src="//fb.me/react-0.12.2.js"></script> <script src="//fb.me/JSXTransformer-0.12.2.js"></script> <script type="text/jsx" src="example1.jsx"></script> </head> <body>
example2 和 example3 使用 bower 包管理器來安裝 React;它們引用本地抓取的瀏覽器內 JSX 轉換器副本。例如,example2.html 經過如下方式抓取兩個文件:
<script src="bower_components/react/react-with-addons.js"></script> <script src="bower_components/react/JSXTransformer.js"></script>
在兩種狀況下,均可以修改 JSX 文件並刷新瀏覽器來查看結果,以便試驗一下 React。
使用 React 和 example1.jsx 中的代碼快速建立一個組件:
var MyTextfield = React.createClass({ render: function() { return <input type='text' />; } });
就這麼簡單。
這個簡單的自定義 React 組件(與其餘 React 組件一塊兒顯示在圖 2 中)呈現一個不受控制的 輸入字段,包裝一個底層 HTML <input>
元素。這個組件如今可用做 <MyTextfield>
。您可能注意到,原生 HTML 元素名稱是以小寫字母開頭的,而自定義 React 組件(類)名稱是以大寫字母開頭的。
<MyTextfield>
、<MyLabel>
和 <MyButton>
React 組件example1 中的第二個自定義組件是 <MyButton>
:
var MyButton = React.createClass({ render: function() { return <button>{this.props.textlabel}</button>; } });
<MyButton>
呈現一個自定義的 HTML <button>
元素。此組件演示了傳入的特性(在 React 術語中稱爲 props)。Prop 是組件的傳入參數;它們就像 HTML 標籤上的屬性同樣。組件可經過 this.props
訪問這些屬性來自定義它的呈現。JSX 支持經過 { JavaScript 表達式 }
來對內聯 JavaScript 表達式求值,這一支持可用來呈現特性值。
在圖 2 中,<MyButton>
實例使用其 textlabel
特性上的 OK
來參數化:<MyButton textlabel='OK' />
。
組合是重用 UI 組件的基石。React 使您可以輕鬆地組合現有的 React 組件和原生的 HTML 元素,以構建更復雜的組件。
在 example1 中,自定義 React 組件 —<MyTextfield>
、<MyButton>
和 <MyLabel>
— 組合在 UI 中。JSX 讓此過程既直觀又簡單:
React.render( <div> <MyLabel text={TextLabel} /> <MyTextfield /> <MyButton textlabel='OK' /> </div>, document.getElementById('container') );
前面的 API 調用將組合的組件呈現到虛擬 DOM 中 ID 爲 container
的 <div>
— 元素中。
SyntheticEvent
一般,元素上的屬性是經過回調函數來參數化的,而不是經過簡單的數據類型。例如,您可能擁有設置 HTML <button>
元素的 onClick
屬性的經驗。一樣的技術也可應用於 React 組件。在第二個示例 example2.jsx 中,自定義 <MyButton>
React 組件獲取一個 onClick
處理函數回調做爲其特性之一:
var MyButton = React.createClass({ _buttonClicked: function(e) { if (this.props.onClick) { this.props.onClick(e) } }, render: function() { return <button onClick={this._buttonClicked}> {this.props.textlabel}</button>; } });
單擊該按鈕時,將觸發 HTML <button>
元素的 onClick
事件,React 將這些事件轉發給<MyButton>
的 _buttonClicked()
實現。
必定要知道,_buttonClicked()
不是經過原生瀏覽器 DOM 事件調用的,而經過 React 本身的跨瀏覽器、符合 W3C 的 SyntheticEvent
對象的實例來調用。SyntheticEvent
在不一樣瀏覽器上擁有統一的行爲,包裝了實際的 DOM 原始事件。(包裝的 DOM 事件仍可經過nativeEvent
特性訪問;請參見 參考資料,獲取 SyntheticEvent
的更多信息的連接)。
在 _buttonClicked()
中,該代碼可確保 <MyButton>
本身的 onClick
特性已設置,而後將 SyntheticEvent
轉發到該事件處理函數。
一些組件須要維護一種在呈現期間使用的內部狀態。例如,一個複選框組件須要一種狀態來記住它已被選中。
在 example2.jsx 中,<MyTextfield>
組件維護一種始終反映了當前輸入到該文本字段中的數字值的內部狀態(一個名爲 data
的變量)。爲了向一個狀態變量提供一個初始值,可實現 getInitialState()
。在 <MyTextfield
> 中,data
被初始化爲 1
:
var MyTextfield = React.createClass({ getInitialState: function() { return { data: '1' }; }, ...
React 組件狀態可經過 this.state
進行訪問。並且始終使用 this.state.data
來呈現底層 <input>
元素的值:
render: function() { return <input type='text' onChange={this._entryChanged} value={this.state.data} />; }
此示例是構造 React 組件的典型模式:使用一個組件的狀態和 prop 值來呈現或自定義它。
對於這個 <MyTextfield>
,底層 <input>
元素是受控的,由於它的值始終由 React 組件呈現(而 example1 中的 <MyTextfield>
是不受控的)。
爲了更好地理解 <MyTextfield>
和 example2 的工做原理,能夠試用該示例。將 example2 文件放在一個 Web 服務器以後並訪問 example2.html。圖 3 顯示了您最初看到的結果。行數設置爲 1,由於 this.state.data
的初始值爲 1
。
可在該文本字段中鍵入 1 到 9 之間的任何值;它不會接受其餘任何值。單擊 OK 按鈕時,會在右側顯示的表中生成指定的行數。圖 4 顯示生成了 9 行。
在該文本字段中輸入值時,將觸發 <input>
的 onChange
事件,React 將該事件路由到 <MyTextfield>
的 _entryChanged()
處理函數:
_entryChanged: function(e) { var val = e.target.value; var validated = '1'; if (!(isNaN(val) || (parseInt(val) > 9 ) || (parseInt(val) < 1))) { validated = val; } this.setState({data: validated}); if (this.props.onChange) { this.props.onChange(validated); } }
_entryChanged()
確保輸入的值是 1 到 9 之間(包含 1 和 9)的數字。輸入的任何在該範圍外的值都會設置爲 1
。_entryChanged()
而後使用this.setState()
更新狀態 (data
)。若是 data
設置爲一個新值,該文本字段內容將在 React 下次調用 render()
時更新。所以,<MyTextfield>
的內容始終與 data
狀態變量同步。
此行爲是在 React 組件中常常觀察到的另外一種模式:事件處理函數修改狀態變量,後者進而更改呈現的組件的外觀。
一個 <DynamicList>
React 組件負責依據所提供的 rows
特性,呈現錶行(參見 源代碼 瞭解詳細信息)。表中的每一個單元格是一個<MyTableCell>
實例,其中包裝了一個已禁用的 <input>
元素。
最後但一樣重要的是一個 <ListCreator>
組件,它組合 <MyTextfield>
、<MyButton>
和 <DynamicList>
來建立最終的 UI。單擊 <MyButton>
實例時,_okClicked()
處理函數會在 data
(跟蹤 <MyTextfield>
中的值)與 rows
(用於呈現 <DynamicList>
中的行)之間複製它的狀態值。事件處理函數中的這一狀態更新會致使 <DynamicList>
從新呈現它顯示的行。
shouldComponentUpdate
和 PureRenderMixin
來優化呈現虛擬 DOM 中的呈現已經很快。可是,避免從新呈如今每次呈現時沒有改變的 UI 部分,始終是明智之舉。
React 擁有一個名爲 shouldComponentUpdate
的回調,任何組件均可以實現它。組件可經過返回 false
來阻止沒必要要的呈現 — 只須要知道自上次呈現以來沒有發生變化便可。默認狀況下,它始終返回 true
。
example2 中的 <ListCreator>
組件使用一個來自 React 的 PureRenderMixin
加載項:
var ListCreator = React.createClass({ mixins: [React.addons.PureRenderMixin], ...
這個 mixin 添加了一個 shouldComponentUpdate
實現,後者可大致比較之前的全部 prop 和狀態值與當前值。若是它未檢測到變化,則返回false
— 消除了沒必要要的呈現。若是一個組件的外觀徹底依賴於 prop 和狀態值,就像 <ListCreator>
中同樣,mixin 可幫助優化呈現性能。
比較活躍的大型 React 社區的一個優點是,有大量容易使用的 React 組件可供使用。在 GitHub 等開源存儲庫上隨意搜索,都會找到數百個可用的組件。
在 example3 中,使用了一個第三方條形圖繪製 React 組件 (Mateus Zitelli 建立的 react-bar-chart)來建立 UI。這個示例顯示了一個網站的頁面訪問統計數據(按年齡組分類)。圖 5 顯示了該 UI。
可編輯左側表中的每一個單元格(單擊該單元格或文本字段),該條形圖會經過當即刷新來顯示更新的值。
表 1 描述了 example3 中的自定義組件,可用做自行分析 example3 源代碼的指南。這些組件在圖 5 中具備不一樣的標籤。
React 組件 | 描述 | 重要狀態/prop |
---|---|---|
<MyTableCell> |
一個可更新的表單元格。將一個受控的 <input> 與一個 <button> 組合。在編輯該單元格值時,<input> 已禁用,<button> 被設置爲{display: none} 。 |
editing :跟蹤一個單元格是否在編輯。data :始終反映單元格中顯示的值。 |
<MyTable> |
一個包含多行的表,每行包含一個 <MyTableCell> 實例。 |
無狀態。data 是一個 prop、一個 hash 數組,包含填充每行的 text 和 value 。onUpdate 是一個回調 prop,在執行一次編輯會話以後觸發,用於轉發更改的單元格信息。 |
<MyBarChart> |
一個容器視圖。經過第三方 React 組件呈現一個<BarChart> 實例。與 Flux 存儲交互,以便在站點狀態更改時接收通知。 |
stats :一個 hash 數組,包含 text 和 value ;爲圖表提供數據。 |
<MyTableWrapper> |
一個容器視圖。與 Flux 分派器交互,只要表中的一個單元格值被修改,就發送 sitestats 更新動做。 |
sitestats :一個 hash 數組,包含 text 和 value ;檢查表中的當前值。 |
<EditableChart> |
將 <MyBarChart> 和 <MyTable> 組合到最終的 UI 中。 |
無狀態。data 是一個 prop,它提供從 Flux 存儲抓取的初始數據,用於顯示在組合的組件中。 |
您的大部分代碼都僅適用於虛擬 DOM。可是,一些情形須要訪問實際的瀏覽器 DOM。一個示例就是在須要集成現有庫時,好比 jQuery Mobile(參見 參考資料)和 D3;另外一個示例在 <MyTableCell>
中的 _cellClicked()
處理函數中。單擊該單元格以後,該單元格將進入編輯模式,將焦點放在實際的 <input>
瀏覽器 DOM 元素上。您經過一個對 getDOMNode()
的延遲調用來完成此任務:
setTimeout(function() { this.refs.inp.getDOMNode().select(); }.bind(this), 100);
refs
用於訪問在虛擬 DOM 中呈現期間建立的元素(相似於瀏覽器 DOM 中的 getElementById()
)。
在 <MyTableWrapper>
中,它的全部 prop 都傳輸到通過包裝的 <MyTable>
實例。可是,onUpdate
prop 值必須被覆蓋,並指向它的_dataUpdated
回調。您可使用 JSX 傳播屬性表示法來完成此任務:
<MyTable {...this.props} onUpdate={this._dataUpdated}/>
在 <EditableChart>
中,它的 sitestats
prop 的初始值從一個 Flux 存儲抓取(參見本教程的 Flux:React 應用程序的擴展架構 一節)。使用getDefaultProps
提供這個初始默認值。React 緩存該值以供之後使用:
getDefaultProps: function() { return { // initial default value only - cached by React sitestats: MockSiteStatsStore.getSiteStats() } }
使用常規 CSS 來設置應用程序的樣式。例如,example3.css 包含 CSS3 flexbox 代碼來設置應用程序的樣式:
.containing{ display: -webkit-flex; display: flex; width: 100%; height: 600px; } .left-side { width: 300px; -webkit-flex: none; flex: none; } .right-side { -webkit-flex: 1; flex: 1; }
您能夠注意到,<EditableCell>
中使用了 className
prop 來指定 CSS 類(以免 JSX 關鍵字衝突):
<div className="containing"> <div className="left-side"> <MyTableWrapper data={this.props.sitestats} /> </div> <div className="right-side"> ... </div> </div>
若是您擁有可隨組件的內部狀態而改變的樣式,您也能夠在 JavaScript 中使用內聯樣式來設置它的樣式。<MyTableCell>
包含一個經過內聯 JavaScript 來設置樣式的原生 <button>
:
<button onClick={this._okClicked} style={this.state.editing? {zIndex: 5}: {display:'none'}}> ok</button>
Flux 是一種搭建使用 React 組件的應用程序的方法。它規定了一種單向數據流架構,可消除與複雜 UI 中存在的互聯 MVC 網絡相關的問題,並改善了代碼庫的長期可維護性。
簡單地講,組件呈現過程當中使用的共享的易變應用程序狀態是向上遊推送的。控制器-視圖(上級擁有者視圖 — 好比 example3 中的<MyBarChart>
和 <MyTableWrapper>
— 管理擁有的子視圖要呈現的狀態)取代了傳統的控制器。傳統的模型操做如今的執行方式是,經過一個單獨的分派器將動做 路由到相關的存儲。動做是聲明要執行的操做和它的關聯數據的數據包。(緊密耦合的方法調用由動做轉換爲鬆散耦合的數據流。)存儲和它們的處理的相互依賴性由分派器以一種鬆散耦合的方式處理。(設計模式的開發者可能會看到命令和責任鏈模式的類似性;系統工程師可能會考慮編組 (marshaling) 和序列化。)
控制器-視圖毫不會直接或間接地彼此共享應用程序狀態。它們向存儲註冊其關注的數據更改,存儲通知視圖在更改發生時抓取數據(並更新其本身的託管狀態)。這是進入這些視圖的唯一的數據流。圖 6 演示了這種單向數據流。
example3 代碼大致遵循 Flux 的構造模式,只有一個模擬的存儲實現。<MyBarChart>
向 MockSiteStatsStore
註冊來獲取任何數據更新。來自存儲的通知是更改 <MyBarChart>
的託管狀態的唯一方式。
在 <MyTableWrapper>
中執行的任何交互式的數據更改,都會以動做的形式經過 MockSiteStatsAction.update()
調用投入分配器/存儲。即便這兩個組件並存於同一個 UI 中,它們也不會直接共享狀態。單向數據流是從 <MyTableWrapper>
流經分派器,再流到相關的MockSiteStatsStore
,而後經過更改通知返回到 <MyBarChart>
。
Flux 應用程序構建模式的詳細介紹不屬於本文的範疇。參見 參考資料,瞭解有關的更多信息。
React Developer Tools 是一個有用的 Chrome 瀏覽器擴展,能夠經過 Chrome Web 存儲 獲取。使用 Chrome Devtools 進行調試時,能夠查看應用程序的 React 組件分層結構,而不是更加神祕的瀏覽器 DOM 表示。圖 7 顯示了安裝了 React Developer Tools 的 example3 組件分層結構外觀。
一個 profiler 也可用做加載項來檢測您的 React 代碼。
對 Flux 的 Relay 擴展和 React Native 都於 2015 年 1 月在 React.js Conf 上公開。截至編寫本文時,兩者都沒有開源。
Relay 擴展了 Flux,以包含服務器數據抓取功能。Relay 背後的關鍵理念是,使每一個 React 組件可以獨立地指定本身的數據抓取需求。一般,這會引用用於呈現組件自己的數據。Relay 將支持在 UI 邏輯所在的同一個 (JSX) 文件內靜態地聲明組件的數據需求。這顯著減小了在處理複雜的完整應用程序時典型的文件雜亂現象。它還容許經過在編輯 JSX 文件後刷新瀏覽器,在來發期間即時驗證正確性和同步。
支撐 Relay 的技術是 GraphQL,這是一種適合任意格式數據的可組合的聲明式查詢規範語言。(Facebook 在生產環境中使用 GraphQL 已有兩年。)GraphQL 使更高級的 React 組件可以組合其本身的組件的需求(無需依賴於需求自己的詳細信息),並構建一個可發送給服務器的組合查詢。
來自 GraphQL 查詢的結果存儲到一個 general common
Flux 存儲中,其中會通知註冊了興趣的視圖執行更新。
React Native 取代了用於 Android 或 iOS 平臺的瀏覽器 DOM。它使得 React 開發人員可以針對移動設備開發其應用程序。
在 React Native 下,JavaScript 代碼使用一個原生解釋器在本身的系統線程上運行。一個所謂的高性能異步批處理橋將您的 React 代碼鏈接到原平生臺 — 編排原生 UI 組件來實現您的 UI。您也可經過此橋公開自定義的 JavaScript 訪問原生訪問。React 中的 HTML 元素已被移動平臺上的原生代碼視圖和 UI 小部件取代。React Native 將經過一個基於 JavaScript 的 CSS 子集來設置原生組件的樣式。
依據開發團隊,使用 React Native 建立的應用程序可很好地再現用戶僅與原生應用程序關聯的細微的 UI 細節 — 基於 WebViews 或 HTML5 的替代方法目前沒法獲得此水平。
JavaScript 開發人員在爲其項目建立 UI 時,可從豐富的開源框架和庫中進行選擇。但不多有框架和庫經受得住注重性能的複雜 UI 項目的考驗;擁有複雜的工具支持的框架和庫更少;具備成熟的編碼和設計最佳實踐,以及一種擴展到服務器端數據操做和移動開發的有保障的將來發展路徑的框架和庫少之又少。React 具備全部這些優點,同時還擁有不斷壯大、繁榮的社區的支持。如今是將 React 添加到您的開發工具箱中的絕佳時機。
描述 | 名字 | 大小 |
---|---|---|
本文示例的源代碼 | wa-react-intro.zip | 7.97KB |