原文: React Components, Elements, and Instanceshtml
組件與它們實例以及元素之間的區別困擾着不少的React初學者。爲何會有三種不一樣的術語來指代屏幕上的東西?react
若是你是一個React新手,你之前可能只使用過組件類和實例。好比,你經過建立一個class
來聲明一個Button
。app執行過程當中,在屏幕中你會獲得這個組件的幾個實例,每個都有本身的屬性和本地狀態。這是傳統的面向對象編程。那麼爲何要介紹元素呢?git
在傳統的UI模式中,你須要本身去建立和銷燬子組件實例。若是一個Form
組件想要渲染一個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);
}
}
}
複製代碼
上面是Form
組件的僞代碼,可是這些或多或少是你編寫組合UI代碼的最終結果,這些代碼使用一個庫好比BackBone,以面向對象的方式達到行爲一致性。算法
每一個組件實例必須保持對其DOM節點和子組件實例的引用,並在適當的時候建立、更新和銷燬它們。代碼行數將會朝着組件可能狀態數的平方量級增加,而且父組件能夠直接訪問其子組件實例,這使得未來很難解耦它們。編程
那麼,React有什麼不一樣呢?react-native
在React中,這就是元素髮揮做用的地方。元素是描述組件實例或DOM節點及其所需屬性的普通對象。 它只包含關於組件類型(例如,按鈕)、屬性(例如,顏色)和其中一些子元素的信息。api
元素不是真實的實例,而是一種告訴React你想在屏幕上看到什麼的方式。咱們不能調用元素上面的任何方法,由於它只是一個不可變的描述對象,有兩個字段:type: (string | ReactClass)
和props: object
。數組
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}
複製代碼
這個元素只是將下列HTML表示爲普通對象的一種方式:安全
<button class='button button-blue'>
<b>
OK!
</b>
</button>
複製代碼
請注意元素是如何被嵌套的。按照慣例,在建立元素樹時,咱們指定一個或多個子元素做爲其包含元素的children
屬性。
重要的是,子元素和父元素都只是描述,而不是真實的實例。當你建立它們時,它們不會指向屏幕上的任何東西。你能夠創造它們,而後把它們扔掉,這並不會產生其餘影響。
React元素很容易遍歷,不須要解析,而且它們比實際的DOM元素輕量得多——由於它們只是對象!
事實上,元素的類型(type)也能夠是對應於React組件的函數(function)或類(class),就像下面這樣:
{
type: Button,
props: {
color: 'blue',
children: 'OK!'
}
}
複製代碼
這是React的核心理念。
描述組件的元素也是元素,就像描述DOM節點的元素同樣。它們能夠相互嵌套和混合。
這個特色讓你定義一個DangerButton
組件爲一個Button
,能夠具備特定的color
屬性,而不須要擔憂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>
);
複製代碼
這種混合和匹配有助於保持組件之間的解耦,由於它們能夠僅經過組合來表達「繼承」和「從屬」關係:
Button
是一個具備特定屬性的DOM < Button >
。DangerButton
是一個具備特定屬性的Button
。DeleteAccount
在div
中包含了一個Button
和一個DangerButton
。當React看到一個帶有函數(function)或類(class)類型的元素時,它知道詢問該組件應該渲染哪一個元素,並給出相應的屬性。
當React看到下面這個元素時:
{
type: Button,
props: {
color: 'blue',
children: 'OK!'
}
}
複製代碼
React會詢問Button
應該渲染什麼。而後Button
會返回下面的元素:
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}
複製代碼
React將重複此過程,直到它知道頁面上每一個組件的底層DOM標記元素。
React就像一個孩子同樣,當你向他們解釋每個「X是Y」時會問「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組件來講,輸入是屬性,輸出是元素樹。
返回的元素樹能夠包含描述DOM節點的元素和描述其餘組件的元素。這使你能夠組合相互獨立的UI部分,而不依賴於它們的內部DOM結構。
咱們使用React建立、更新和銷燬實例,用從組件返回的元素來描述它們,而React負責管理這些實例。
在上面的代碼中,Form
, Message
, 和 Button
都是React組件。它們既能夠被寫成函數形式就像上面代碼同樣,也能夠寫成類的形式繼承自React.Component
。這三種聲明組件的方式是等效的。
// 1) As a function of props
const Button = ({ children, color }) => ({
type: 'button',
props: {
className: 'button button-' + color,
children: {
type: 'b',
props: {
children: children
}
}
}
});
// 2) Using the React.createClass() factory
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
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
}
}
}
};
}
}
複製代碼
當一個組件被定義爲一個類時,它比一個函數組件更強大一些。它能夠存儲一些本地狀態,並在建立或銷燬相應的DOM節點時執行自定義邏輯。
函數組件功能不太強大,可是比較簡單,它就像一個類組件,只有一個render()方法。除非您須要只在類中可用的特性,不然咱們鼓勵您使用函數組件。
然而,不管是函數組件仍是類組件,它們本質上都是React的組件。它們將屬性做爲輸入,並將元素做爲輸出返回。
當你調用:
ReactDOM.render({
type: Form,
props: {
isSubmitted: false,
buttonText: 'OK!'
}
}, document.getElementById('root'));
複製代碼
React將詢問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調用協調算法過程的一部分,它是在調用ReactDOM.render()
和 setState()
時被觸發執行。在協調結束時,React知道結果DOM樹,而像react-dom或react-native這樣的渲染器會使用所需的最小變化集合更新到DOM節點(在React Native的狀況下,會應用特定於平臺的視圖)。
這種漸進的解析過程也是React應用程序易於優化的緣由所在。若是組件樹的某些部分太大,以至React沒法有效訪問。若是相關的屬性沒有改變,你能夠告訴它跳過這個「細節」只diffing
組件樹的某些部分。若是這些屬性是不可變的,那麼計算它們是否改變是很是快的,因此React和immutability能夠很好地協同工做,而且能夠用最小的努力提供最佳優化。
您可能已經注意到,這篇博客文章主要討論了組件和元素,而不是實例。事實是,與大多數面向對象的UI框架相比,React中的實例的重要性要小得多。
只有聲明爲類的組件纔有實例,並且你永遠不會直接建立它們,React爲你作這些。因爲父組件實例訪問子組件實例機制已經存在,它們只用於必要的操做(例如設置字段的焦點),一般應該避免使用。
React負責爲每一個類組件建立一個實例,所以可使用方法和本地狀態以面向對象的方式編寫組件,但除此以外,實例在React的編程模型中不是很重要,由React本身管理。
元素是一個普通對象,它根據DOM節點或其餘組件描述但願在屏幕上顯示的內容。元素能夠在其屬性中包含其餘元素。建立一個React元素很便宜。一旦建立了一個元素,它就不會發生改變。
組件能夠用幾種不一樣的方式聲明。它能夠是一個帶有render()方法的類。或者在簡單的狀況下,能夠將它定義爲一個函數。在這兩種狀況下,它都接受屬性做爲輸入,並返回一個元素樹做爲輸出。
當一個組件接收到一些屬性做爲輸入時,它就變成了一個特定的父組件,返回一個元素及其類型和這些屬性。這就是爲何人們說這些屬性是單向流動的:從父到子。
實例就是在編寫的組件類中引用爲this
的實例。它對於存儲本地狀態和響應生命週期事件很是有用。
函數組件沒有實例。類組件有實例,但你不須要直接建立組件實例—react負責這方面的工做。
最後,建立元素可使用React.createElement()
, JSX
或者element factory helper
.不要在實際代碼中將元素編寫爲普通對象—要知道它們是隱藏在底層的普通對象。
出於安全因素,全部的React元素對象須要一個額外的字段 -
$$typeof: Symbol.for('react.element')
聲明。這是上文中遺漏的地方。本篇文章使用元素的內聯對象來讓你瞭解底層發生了什麼,可是除非你向元素添加$$typeof
,或者更改代碼以使用react. createElement()
或JSX
,不然代碼不會按預設的邏輯運行。