做者:Dan Abramov
譯者:Jogis
譯文連接:https://github.com/yesvods/Blog/issues/5
轉載請註明譯文連接以及譯者信息html
不少React新手對Components以及他們的instances和elements之間的區別感到很是困惑,爲何要用三種不一樣的術語來表明那些被渲染在熒屏上的內容呢?react
若是是剛入門React,那麼你應該只是接觸過一些組件類(component classes)以及實例(instances)。打比方,你可能經過class
關鍵字聲明瞭一個Button
組件。這個程序運行時候,可能會有幾個Button
組件的實例(instances)運行在瀏覽器上,每個實例會有各自的參數(properties)以及本地狀態(state)。這種屬於傳統的面向對象UI編程。那麼爲何會有元素(elements)出現呢?git
在這種傳統UI模式上,你須要負責建立和刪除實例(instances)的子組件實例。若是一個Form
的組件想要渲染一個Button
子組件,須要實例化這個Button
子組件,而且手動更新他們的內容。github
class Form extends TraditionalObjectOrientedView { render() { // Read some data passed to the view const { isSubmitted, buttonText } = this.attrs; if (!isSubmitted && !this.button) { // Form is not yet submitted. Create the button! this.button = new Button({ children: buttonText, color: 'blue' }); this.el.appendChild(this.button.el); } if (this.button) { // The button is visible. Update its text! this.button.attrs.children = buttonText; this.button.render(); } if (isSubmitted && this.button) { // Form was submitted. Destroy the button! this.el.removeChild(this.button.el); this.button.destroy(); } if (isSubmitted && !this.message) { // Form was submitted. Show the success message! this.message = new Message({ text: 'Success!' }); this.el.appendChild(this.message.el); } } }
這個只是僞代碼,可是這個就是大概的形式。特別是當你用一些庫(好比Backbone),去寫一些須要保持數據同步的組件化組合的UI界面時候。編程
每個組件實例須要保留它的DOM節點引用和子組件的實例,而且須要在合適時機去建立、更新、刪除那些子組件實例。代碼行數會隨着組件的狀態(state)數量,以平方几何級別增加。並且這樣,組件須要直接訪問它的子組件實例,使得這個組件之後很是難解耦。react-native
因而,React又有什麼不一樣呢?api
React提出一種元素(elements)來解決這個問題。一個元素僅僅是一個純的JSON對象,用於描述這個組件的實例或者是DOM節點(譯者注:好比div)和組件所須要的參數。元素僅僅包括三個信息:組件類型(例如,Button
)、組件參數(例如:color
)和一些組件的子元素數組
一個元素(element)實際上並不等於組件的實例,更確切地說,它是一種方式,去告訴React在熒屏上渲染什麼,你並不能調用元素的任何方法,它僅僅是一個不可修改的對象,這個對象帶有兩個字段:type: (string | ReactClass)
和props: Object
1瀏覽器
當一個元素的type
是一個字符串,表明是一個type
(譯者注:好比div)類型的DOM,props
對應的是這個DOM的屬性。React就是根據這個規則來渲染,好比:安全
{ type: 'button', props: { className: 'button button-blue', children: { type: 'b', children: 'OK!' } } }
這個元素只是用一個純的JSON對象,去表明下面的HTML:
<button class='button button-blue'> <b> OK! </b> </button>
須要注意的是,元素之間是怎麼嵌套的。按照慣例,當咱們想去建立一棵元素樹(譯者注:對,有點拗口),咱們會定義一個或者多個子元素做爲一個大的元素(容器元素)的children
參數。
最重要的是,父子元素都只是一種描述符,並非實際的實例(instances)。在他們被建立的時候,他們不會去引用任何被渲染在熒屏上的內容。你能夠建立他們,而後把他們刪掉,這並不會對熒屏渲染產生任何影響。
React元素是很是容易遍歷的,不須要去解析,理所固然的是,他們比真實的DOM元素輕量不少————由於他們只是純JSON對象。
然而,元素的type
屬性可能會是一個函數或者是一個類,表明這是一個React組件:
{ type: Button, props: { color: 'blue', children: 'OK!' } }
這就是React的核心靈感!
一個描述另一個組件的元素,依舊是一個元素,就像剛剛描述DON節點的元素那樣。他們能夠被嵌套(nexted)和相互混合(mixed)。
這種特性可讓你定義一個DangerButton
組件,做爲一個有特定Color
屬性值的Button
組件,而不須要擔憂Button
組件實際渲染成DOM的適合是button
仍是div
,或者是其餘:
const DangerButton = ({ children }) => ({ type: Button, props: { color: 'red', children: children } });
在一個元素樹裏面,你能夠混合配對DOM和組件元素:
const DeleteAccount = () => ({ type: 'div', props: { children: [{ type: 'p', props: { children: 'Are you sure?' } }, { type: DangerButton, props: { children: 'Yep' } }, { type: Button, props: { color: 'blue', children: 'Cancel' } }] });
或者可能你更喜歡JSX:
const DeleteAccount = () => ( <div> <p>Are you sure?</p> <DangerButton>Yep</DangerButton> <Button color='blue'>Cancel</Button> </div> );
這種混合配對有利於保持組件的相互解耦關係,由於他們能夠經過組合(componsition)獨立地表達is-a()
和has-a()
的關係:
Button
是一個附帶特定參數的<button>
DOM
DangerButton
是一個附帶特定參數的Button
DeleteAccount
在一個<div>
DOM裏包含一個Button
和一個DangerButton
當React看到一個帶有type
屬性的元素,並且這個type
是個函數或者類,React就會去把相應的props
給予元素,而且去獲取元素返回的子元素。
當React看到這種元素:
{ type: Button, props: { color: 'blue', children: 'OK!' } }
就會去獲取Button
要渲染的子元素,Button
就會返回下面的元素:
{ type: 'button', props: { className: 'button button-blue', children: { type: 'b', children: 'OK!' } } }
React會不斷重複這個過程,直到它獲取這個頁面全部組件潛在的DOM標籤元素。
React就像一個孩子,會去問「什麼是 Y」,而後你會回答「X 是 Y」。孩子重複這個過程直到他們弄清楚這個世界的每個小的細節。
還記得上面提到的Form
例子嗎?它能夠用React來寫成下面形式:
const Form = ({ isSubmitted, buttonText }) => { if (isSubmitted) { // Form submitted! Return a message element. return { type: Message, props: { text: 'Success!' } }; } // Form is still visible! Return a button element. return { type: Button, props: { children: buttonText, color: 'blue' } }; };
就是這麼簡單!對於一個React組件,props
會被做爲輸入內容,一個元素會被做爲輸出內容。
被組件返回的元素樹可能包含描述DOM節點的子元素,和描述其餘組件的子元素。這可讓你組合UI的獨立部分,而不須要依賴他們內部的DOM結構。
React會替咱們建立更新和刪除實例,咱們只須要經過組件返回的元素來描述這些示例,React會替咱們管理好這些實例的操做。
在上面提到的例子裏,Form
,Message
和Button
都是React組件。他們均可以被寫成函數形式,就像上面提到的,或者是寫成繼承React.Component
的類的形式。這三種聲明組件的方法結果幾乎都是相同的:
// 1) As a function of props // 1) 做爲一個接收props參數的函數 const Button = ({ children, color }) => ({ type: 'button', props: { className: 'button button-' + color, children: { type: 'b', props: { children: children } } } }); // 2) Using the React.createClass() factory // 2) 使用React.createClass()的工廠方法 const Button = React.createClass({ render() { const { children, color } = this.props; return { type: 'button', props: { className: 'button button-' + color, children: { type: 'b', props: { children: children } } } }; } }); // 3) As an ES6 class descending from React.Component // 3) 做爲一個ES6的類,去繼承React.Component class Button extends React.Component { render() { const { children, color } = this.props; return { type: 'button', props: { className: 'button button-' + color, children: { type: 'b', props: { children: children } } } }; } }
當組件被定義爲類,它會比起函數方法的定義強大一些。它能夠存儲一些本地狀態(state)以及在相應DOM節點建立或者刪除時候去執行一些自定義邏輯。
一個函數組件會沒那麼強大,可是會更簡潔,並且能夠經過一個render()
就能表現得就像一個類組件同樣。除非你須要一些只能用類才能提供的特性,不然咱們鼓勵你去使用函數組件來替代類組件。
然而,不論是函數組件或者類組件,基原本說,他們都屬於React組件。他們都會以props做爲輸入內容,以元素做爲輸出內容
當你調用:
ReactDOM.render({ type: Form, props: { isSubmitted: false, buttonText: 'OK!' } }, document.getElementById('root'));
React會提供那些props
去問Form
:「請你返回你的元素樹」,而後他最終會使用簡單的方式,去「精煉」出他對於你的組件樹的理解:
// React: You told me this... { type: Form, props: { isSubmitted: false, buttonText: 'OK!' } } // React: ...And Form told me this... { type: Button, props: { children: 'OK!', color: 'blue' } } // React: ...and Button told me this! I guess I'm done. { type: 'button', props: { className: 'button button-blue', children: { type: 'b', props: { children: 'OK!' } } } }
這部分過程被React稱做協調(reconciliation),在你調用ReactDOM.render()
或者setState()
的時候會被執行。在協調過程結束以前,React掌握DOM樹的結果,再這以後,好比react-dom
或者react-native
的渲染器會應用最小必要變動集合來更新DOM節點(或者是React Native的特定平臺視圖)。
這個逐步精煉的過程也說明了爲何React應用如此容易優化。若是你的組件樹一部分變得太龐大以致於React難以去高效訪問,在相關參數沒有變化的狀況下,你能夠告訴React去跳過這一步「精煉」以及跳過diff樹的其中一部分。若是參數是不可修改的,計算出他們是否有變化會變得至關快。因此React和immutability結合起來會很是好,並且能夠用最小的代價去得到最大的優化。
你可能發現這篇博客一開始談論到不少關於組件和元素的內容,可是並無太多關於實例的。事實上,比起大多數的面向對象UI框架,實例在React上顯得並無那麼重要。
只有類組件能夠擁有實例,並且你歷來不須要直接建立他們:React會幫你作好。當存在父組件實例訪問子組件實例的狀況下,他們只是被用來作一些必要的動做(好比在一個表單域設置焦點),並且一般應該要避免這樣作。
React爲每個類組件維護實例的建立,因此你能夠以面向對象的方式,用方法和本地狀態去編寫組件,可是除此以外,實例在React的變成模型上並非很重要,並且會被React本身管理好。
元素是一個純的JSON對象,用於描述你想經過DOM節點或者其餘組件在熒屏上展現的內容。元素能夠在他們的參數裏面包含其餘元素。建立一個React元素代價很是小。一個元素一旦被建立,將不可更改。
一個組件能夠用幾種不一樣的方式去聲明。能夠是一個帶有render()
方法的類。做爲另一種選擇,在簡單的狀況下,組件能夠被定義爲一個函數。在兩種方式下,組件都是被傳入的參數做爲輸入內容,以返回的元素做爲輸出內容。
若是有一個組件被調用,傳入了一些參數做爲輸入,那是由於有一某個父組件返回了一個帶有這個組件的type
以及這些參數(到React上)。這就是爲何你們都認爲參數流動方式只有一種:從父組件到子組件。
實例就是你在組件上調用this時候獲得的東西,它對本地狀態存儲以及對響應生命週期事件很是有用。
函數組件根本沒有實例,類組件擁有實例,可是你歷來都不須要去直接建立一個組件實例——React會幫你管理好它。
最後,想要建立元素,使用React.createElement()
,JSX或者一個元素工廠工具。不要在實際代碼上把元素寫成純JSON對象——僅須要知道他們在React機制下面以純JSON對象存在就好。