React組件
組件可以將UI切分成一些獨立的、可復用的部件,這樣你就只需專注于構建每一個單獨的部件。
組件從概念上看就像是函數,它可以接收任意的輸入值(稱之為“props”),并返回一個需要在頁面上展示的React元素。
函數定義/類定義組件
定義一個組件最簡單的方式是使用JavaScript函數:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
該函數是一個有效的React組件,它接收一個單一的“props”對象并返回了一個React元素。我們之所以稱這種類型的組件為函數定義組件,是因為從字面上來看,它就是一個JavaScript函數。
你也可以使用 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>;
}
// ...
class App extends Component {
render() {
return (
<div className="App">
<Welcome name="Sara" />
</div>
);
}
}
// ...
我們來回顧一下在這個例子中發生了什么:
- 我們對
<Welcome name="Sara" />
元素調用了ReactDOM.render()
方法。 - React將
{name: 'Sara'}
作為props傳入并調用Welcome
組件。 -
Welcome
組件將<h1>Hello, Sara</h1>
元素作為結果返回。 - 將DOM更新為
<h1>Hello, Sara</h1>
。
警告:
組件名稱必須以大寫字母開頭。
例如,
<div />
表示一個DOM標簽,但<Welcome />
表示一個組件,并且在使用該組件時你必須定義或引入它。
組合組件
組件可以在它的輸出中引用其它組件,這就可以讓我們用同一組件來抽象出任意層次的細節。在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>
);
}
通常,一個新的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>
);
}
這個組件接收author
(對象)、text
(字符串)、以及date
(Date對象)作為props,用來描述一個社交媒體網站上的評論。
這個組件由于嵌套,變得難以被修改,可復用的部分也難以被復用。所以讓我們從這個組件中提取出一些小組件。
首先,我們來提取Avatar
組件:
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
Avatar
作為Comment
的內部組件,不需要知道是否被渲染。因此我們將author
改為一個更通用的名字user
。
我們建議從組件自身的角度來命名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>
);
}
提取組件一開始看起來像是一項單調乏味的工作,但是在大型應用中,構建可復用的組件完全是值得的。當你的UI中有一部分重復使用了好幾次(比如,Button
、Panel
、Avatar
),或者其自身就足夠復雜(比如,App
、FeedStory
、Comment
),類似這些都是抽象成一個可復用組件的絕佳選擇,這也是一個比較好的做法。
Props的只讀性
無論是使用函數或是類來聲明一個組件,它決不能修改它自己的props。來看這個sum
函數:
function sum(a, b) {
return a + b;
}
類似于上面的這種函數稱為“純函數”,它沒有改變它自己的輸入值,當傳入的值相同時,總是會返回相同的結果。
與之相對的是非純函數,它會改變它自身的輸入值:
function withdraw(account, amount) {
account.total -= amount;
}
React是非常靈活的,但它也有一個嚴格的規則:
所有的React組件必須像純函數那樣使用它們的props。
當然,應用的界面是隨時間動態變化的,后面會介紹一種稱為“state”的新概念,State可以在不違反上述規則的情況下,根據用戶操作、網絡響應、或者其他狀態變化,使組件動態的響應并改變組件的輸出。
組件的生命周期
React組件的生命周期圖解
組件會隨著組件的props和state改變而發生變化,它的DOM也會有相應的變化。一個組件就是一個狀態機:對于特定的輸入,它總會返回一致的輸出。
React組件提供了生命周期的鉤子函數去響應組件不同時刻的狀態,組件的生命周期如下:
- 實例化階段
- 存在期階段
- 銷毀期階段
實例化階段
首次調用組件時,實例化階段有以下方法會被調用(注意順序,從上到下先后執行):
getDefaultProps
這個方法是用來設置組件默認的props,組件生命周期只會調用一次。但是只適合React.createClass
直接創建的組件,使用ES6/ES7創建的這個方法不可使用,ES6/ES7可以使用下面方式:
//es7
class Component {
static defaultProps = {}
}
getInitialState
設置state初始值,在這個方法中你已經可以訪問到this.props。getDefaultProps只適合React.createClass使用。使用ES6初始化state方法如下:
class Component extends React.Component {
constructor(props){
super(props);
this.state = {
render: true,
}
}
}
// 或者這樣, es6 stage3
class Component extends React.Component{
state = {
render: true
}
render(){return false;}
}
componentWillMount
此方法會在組件首次渲染之前調用,這個是在render方法調用前可修改state的最后一次機會。這個方法很少用到。
render
這個方法以后大家都應該會很熟悉,JSX通過這里,解析成對應的虛擬DOM,渲染成最終效果。格式大致如下:
class Component extends React.Component{
render(){
return (
<div></div>
)
}
}
componentDidMount
這個方法在首次真實的DOM渲染后調用(僅此一次)當我們需要訪問真實的DOM時,這個方法就經常用到。如何訪問真實的DOM這里就不想說了。當我們需要請求外部接口數據,一般都在這里處理。
存在期
實例化后,當props或者state發生變化時,下面方法依次被調用:
componentWillReceiveProps
沒當我們通過父組件更新子組件props時(這個也是唯一途徑),這個方法就會被調用。
componentWillReceiveProps(nextProps){}
shouldComponentUpdate
字面意思,是否應該更新組件,默認返回true。當返回false時,后期函數就不會調用,組件不會在次渲染。
shouldComponentUpdate(nextProps,nextState){}
componentWillUpdate
字面意思組件將會更新,props和state改變后必調用。
render
跟實例化時的render一樣,不多說
componentDidUpdate
這個方法在更新真實的DOM成功后調用,當我們需要訪問真實的DOM時,這個方法就也經常用到。
銷毀期
銷毀階段,只有一個函數被調用:
componentWillUnmount
沒當組件使用完成,這個組件就必須從DOM中銷毀,此時該方法就會被調用。當我們在組件中使用了setInterval,那我們就需要在這個方法中調用clearTimeout。
參考:React組件生命周期