Components使得UI被劃分為一個個獨立、可復用的零件,并單獨考慮每個零件。
在概念上,組件就像是JavaScript函數。他們接受任意的輸入(稱為"props"),并返回React elements來描述屏幕上應該出現的內容。
函數式Components和class Components
定義一個Component最簡單的方法就是寫個JavaScript函數:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
這個函數是一個有效的React component,因為他接受一個名為'props'對象的參數,并且返回一個React element。我們稱這種組件為'函數式'的,因為他們就是普通的JavaScript函數。
你也可以用一個ES6 class來定義component:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
從React的角度來看,上面這兩個components是相同的。
我們將在下一節討論,Classes方式中的一些額外特性。在那之前,方便起見,我們將使用函數式的components。
渲染Component
之前,我們至于見過表現DOM標簽的React elements:
const element = <div />;
不僅如此,elements也可以表示用戶自定義的components:
const element = <Welcome name="Sara" />
當React發現一個表示用戶自定義組件的element,它會把JSX屬性作為一個單獨的對象傳給這個component。我們將這個對象成為'props'。
舉個例子,下面的代碼在頁面上渲染出"Hello, Sara":
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
在CodePen上試試看
讓我們重新過一遍這個例子中發生了什么:
- 我們調用
ReactDOM.render()
函數去渲染element:<Welcome name="Sara" />
。 - React調用
Wecome
組件,將{name: 'Sara'}
作為props。 - 我們的
Welcome
組件返回一個element:<h1>Hello, Sara</h1>
作為結果。 - React DOM高效地更新DOM來匹配
<h1>Hello, Sara</h1>
。
警告:
component名字的首字母始終都是大寫。
比如,<div />
表示一個DOM標簽,但是<Welcome />
表示一個component,而且需要將Welcome
加入當前范圍內。
組合Components
Components可以在他們的輸出中引用其他的components。這樣我們就可以使用相同的組件來疊加完成任意的細節。按鈕,表單,對話框和屏幕,在React應用中,這些通常都表示為組件。
比如:我們可以創建一個App
組件用來渲染多個Welcome
:
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')
);
在CodePen上試一試
通常,新的React應用在最上層有一個單獨的App組件。但是如果你將React集成到現有的應用中,你可以自下而上,從一個小的component開始,比如一個按鈕,逐步的整合到層次結構的頂層。
警告:
Components必須返回一個單獨的根element。這就是為什么我們添加一個<div>
來包括所有的<Welcome />
elements。
提取Components
大膽的將components分割成更小的components。
比如,考慮這個Comment
component:
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>
);
}
在CodePen上試一試
它接收author
(一個object),text
(一個字符串)和date
(一個date)作為props,描述了社交媒體網站上一則評論。
由于全是嵌套,這個component很難改變,而且也很難去重用他的各個部分。讓我們從中提取幾個組件。
首先,我們來提取Avatar
:
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
Avatar
不需要知道Comment
里面究竟在渲染什么東西。因此我們傳給prop一個更為通用的名字:user
,而不是author
。
我們建議從組件自身的觀點來命名props,而不是依據組件所在的環境。
現在我們就稍微簡化了下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>
);
}
接下來,我們將提取一個UserInfo
組件,他渲染一個Avatar
和用戶的名字:
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>
);
}
在CodePen上試一試
提取components也許乍看之下像是無用功,但在大型的app中,能有一系列可重用的組件,這么做絕對超值。一個很好的經驗法則:如果你的UI中某部分被重復使用多次(按鈕
,面板
,頭像
),或者自身相當復雜(App
,FeedStory
,評論
), 這都是成為可重用組件的最佳人選。
Props是只讀的
不論你將component聲明為函數還是類,都絕不能去修改它的props。看下這個sum
函數:
function sum(a, b) {
return a + b;
}
這樣的函數我們稱其為"pure",因為他們不回去改變輸入,且對于輸入一樣返回的結果始終都是相同的。
相比之下,這個函數不是純函數,因為它改變了自己的輸入:
function withdraw(account, amount) {
account.total -= amount;
}
React雖然相當靈活,但它有一個嚴格的規則:
所有的React組件必須像個純函數一樣,不得修改他們的props。
當然,應用的UI是動態的,隨著事件而變化。在下一節,我們將引入一個新的概念:"state"。State使得React components可以根據用戶的行為,網絡響應或者其他什么,使得他們的輸出每次不同,而不會違反此規則。