組件可以讓你將UI分割成獨立的,可復用的模塊,然后考慮將每個模塊彼此隔離。
從概念上理解,組件就像js中的函數。他們接受隨意的輸入(被稱為props)然后返回React元素來描述屏幕上應該出現什么。
函數式和類式組件
定義一個組件最簡單的方法是寫一個js函數:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
這個函數是一個有效的React組件因為它接受一個props對象作為參數然后返回一個React元素。我們把這樣的叫做函數式組件因為他們就是字面意思上js函數。
你也可以使用ES6的class來定義一個組件:
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
從React的角度來看,以上兩種方式定義的組件是一樣的。
類定義這種方法有一些額外的特性我們在下一章節會討論。在此之前,我們會使用函數來定義組件因為這樣比較簡潔。
渲染一個組件
先前,我們只遇到React元素代表DOM標簽的情況:
const element = <div />;
然而,React元素也可以代表用戶自定義組件:
const element = <Welcome name="Sara" />;
當React認為一個元素代表用戶自定義組件時,它通過單個對象傳遞JSX屬性到這個組件。我們叫這個對象為“props”。
舉個例子,這段代碼渲染“Hello, Sara”到頁面上:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; } const element = <Welcome name="Sara" />; ReactDOM.render( element, document.getElementById('root') );
讓我們概括一下在這個例子中到底發生了什么:
- 我們調用了ReactDOM.render()來渲染<Welcome name="Sara"/>元素。
- React調用Welcome組件並將{name: 'Sara'}這個對象作為props傳入。
- Welcome組件返回了一個<h1>Hello, Sara</h1>元素作為結果。
- React DOM高效地更新了DOM來匹配<h1>Hello, Sara</h1>。
附加說明:
總是將組件名的首字母大寫。
舉個例子,<div/>代表一個DOM標簽,但是<Welcome />代表一個一個組件並且需要Welcome定義或引入。
組合組件
組件可以在它的輸出中引用其他組件。這樣我們可以使用同一個組件抽象出任意等級的細節。一個按鈕,一個表單,一個目錄,一個對話框,整個屏幕內容:在React應用中,所有這些都同樣表示為組件。
舉個例子,我們創建一個渲染Welcome組件多次的App組件:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; } function App() { return ( <div> <Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
通常,新的React應用有一個單獨的App組件在最頂端。然而,如果你整合React到一個已經存在的應用里,也許你會從底開始通過小組件類似Button逐漸替換一直到最高級別。
附加說明:
組件必須返回一個單個的根元素。這就是為什么我們添加了一個<div>去包含所有<Welcome />元素。
提取組件
不要害怕將組件分割成更小的組件。
舉個例子,看看這個Comment組件:
function Comment(props) { return ( <div className="Comment"> <div className="UserInfo"> <img className="Avatar" src={props.author.avatarUrl} alt={props.author.name} /> <div className="UserInfo-name"> {props.author.name} </div> </div> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
Comment組件接受了author(一個對象),text(一個字符串)和date(一個日期對象)作為屬性,它在一個社會媒體網站上描述一段評論。
這個組件可以被簡化因為嵌套的東西太多,它很難被復用。讓我們從這里提取出一些組件。
首先,我們將提取Avatar:
function Avatar(props) { return ( <img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} /> ); }
Avatar組件不需要知道自己將要被渲染到Comment內部。這就是為什么我們給他的屬性一個更通用的名字:user而不是author。
建議在命名屬性名的時候從組件自己的角度來命名而不要從使用時的上下文環境來命名。
現在可以將Comment組件簡化為更小的部分:
function Comment(props) { return ( <div className="Comment"> <div className="UserInfo"> <Avatar user={props.author} /> <div className="UserInfo-name"> {props.author.name} </div> </div> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
function UserInfo(props) { return ( <div className="UserInfo"> <Avatar user={props.user} /> <div className="UserInfo-name"> {props.user.name} </div> </div> ); }
這允許我們更進一步地簡化Comment:
function Comment(props) { return ( <div className="Comment"> <UserInfo user={props.author} /> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
提取組件也許看起來像很枯燥的工作,但是擁有一系列可復用的組件會在大型應用中起到很好的效果。有一個很好的經驗法則就是如果有一部分UI已經被使用多次(如Button,Panel,Avatar),或者組件本身已經足夠復雜(如APP,FeedStory,Comment),那么使它成為一個可復用的組件會更合適。
props是只讀的
如果你聲明了一個組件使用函數或者類,那么它永遠不能修改它自己的props。看看這個sum函數:
function sum(a, b) { return a + b; }
這樣的函數被稱為“純函數”因為它們不嘗試改變它們的輸入,而且在輸入同樣的情況下一直返回一樣的結果。
相比之下,這個函數就不是純函數因為它改變了輸入:
function withdraw(account, amount) { account.total -= amount; }
