function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
當React遇到的元素是用戶自定義的組件,它會將JSX屬性作爲單個對象傳遞給該組件,這個對象稱之爲“props”。
我們來回顧一下在這個例子中發生了什麼:
- 我們對
<Welcome name="Sara" />
元素調用了ReactDOM.render()
方法。 - React將
{name: 'Sara'}
作爲props傳入並調用Welcome
組件。 Welcome
組件將<h1>Hello, Sara</h1>
元素作爲結果返回。- React DOM將DOM更新爲
<h1>Hello, Sara</h1>
。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return ( {/*直接return();*/}
<div>
<Welcome name="Sara" /> {/*在組件中嵌套組件*/}
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
提取組件一開始看起來像是一項單調乏味的工作,但是在大型應用中,構建可複用的組件完全是值得的。當你的UI中有一部分重複使用了好幾次(比如,Button
、Panel
、Avatar
),或者其自身就足夠複雜(比如,App
、FeedStory
、Comment
),類似這些都是抽象成一個可複用組件的絕佳選擇,這也是一個比較好的做法。
function formatDate(date) { //這裏只是一個函數而非組件,首字母沒有大寫
return date.toLocaleDateString();
}
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name} />
);
}
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
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>
);
}
const comment = { //這裏是一個對象嗎
date: new Date(),
text: 'I hope you enjoy learning React!',
author: {
name: 'Hello Kitty',
avatarUrl: 'http://placekitten.com/g/64/64'
}
};
ReactDOM.render(
<Comment
date={comment.date}
text={comment.text}
author={comment.author} />,
document.getElementById('root')
);
Props的只讀性
無論是使用函數或是類來聲明一個組件,它決不能修改它自己的props。來看這個sum
函數:
function sum(a, b) {
return a + b;
}
類似於上面的這種函數稱爲“純函數”,它沒有改變它自己的輸入值,當傳入的值相同時,總是會返回相同的結果。
與之相對的是非純函數,它會改變它自身的輸入值:
function withdraw(account, amount) {
account.total -= amount;
}
React是非常靈活的,但它也有一個嚴格的規則:
所有的React組件必須像純函數那樣使用它們的props。
function tick() { //這裏只是定義了一個函數
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000); //注意這裏的調用tick沒有加()
下面封裝了一個Clock組件
function Clock(props) { //這裏是組件
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() { //這裏是方法
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000); //這裏調用方法
然而,它錯過了一個關鍵的要求:Clock
設置一個定時器並且每秒更新UI應該是Clock
的實現細節。
理想情況下,我們寫一次 Clock
然後它能更新自身:
爲了實現這個需求,我們需要爲Clock
組件添加狀態
狀態與屬性十分相似,但是狀態是私有的,完全受控於當前組件。
我們之前提到過,定義爲類的組件有一些特性。局部狀態就是如此:一個功能只適用於類。
class Clock extends React.Component { //component{render() {return ();} }
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>{/*沒有(props)而是變成了this.props*/}
</div>
);
}
}
function tick() {
ReactDOM.render( //在方法中引入組件,渲染節點
<Clock date={new Date()} />, //props從這裏傳入組件
document.getElementById('root')
);
}
setInterval(tick, 1000);
將函數轉換爲類
你可以通過5個步驟將函數組件 Clock
轉換爲類
創建一個名稱擴展爲
React.Component
的ES6 類創建一個叫做
render()
的空方法將函數體移動到
render()
方法中在
render()
方法中,使用this.props
替換props
刪除剩餘的空函數聲明
class Clock extends React.Component { //component{render() {return ();} }
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>{/*沒有(props)而是變成了this.props*/}
</div>
);
}
}
function tick() {
ReactDOM.render( //在方法中引入組件,渲染節點
<Clock date={new Date()} />, //props從這裏傳入組件
document.getElementById('root')
);
}
setInterval(tick, 1000);
爲一個類添加局部狀態
我們會通過3個步驟將 date
從屬性移動到狀態中:
class Clock extends React.Component {
constructor(props) {
super(props); //注意我們如何傳遞 props 到基礎構造函數的:
this.state = {date: new Date()}; //step-two添加一個類構造函數來初始化狀態 this.state
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>{/*step-one在 render() 方法中使用 this.state.date 替代 this.props.date*/}
</div>
);
}
}
ReactDOM.render(
<Clock />, //step-three從 <Clock /> 元素移除 date 屬性:
document.getElementById('root')
);
將生命週期方法添加到類中
在具有許多組件的應用程序中,在銷燬時釋放組件所佔用的資源非常重要。
每當Clock
組件第一次加載到DOM中的時候,我們都想生成定時器,這在React中被稱爲掛載
同樣,每當Clock
生成的這個DOM被移除的時候,我們也會想要清除定時器,這在React中被稱爲卸載
。
我們可以在組件類上聲明特殊的方法,當組件掛載或卸載時,來運行一些代碼:
class Clock extends React.Component {
constructor(props) { //構造函數
super(props);
this.state = {date: new Date()};
}
componentDidMount() { //當組件輸出到 DOM 後會執行 componentDidMount() 鉤子,這是一個建立定時器的好地方
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID); //在 componentWillUnmount()生命週期鉤子中卸載計時器:
}
tick() { //最後,我們實現了每秒鐘執行的 tick() 方法。它將使用 this.setState() 來更新組件局部狀態:
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
現在時鐘每秒鐘都會執行。
讓我們快速回顧一下發生了什麼以及調用方法的順序:
當
<Clock />
被傳遞給ReactDOM.render()
時,React 調用Clock
組件的構造函數。 由於Clock
需要顯示當前時間,所以使用包含當前時間的對象來初始化this.state
。 我們稍後會更新此狀態。React 然後調用
Clock
組件的render()
方法。這是 React 瞭解屏幕上應該顯示什麼內容,然後 React 更新 DOM 以匹配Clock
的渲染輸出。當
Clock
的輸出插入到 DOM 中時,React 調用componentDidMount()
生命週期鉤子。 在其中,Clock
組件要求瀏覽器設置一個定時器,每秒鐘調用一次tick()
。瀏覽器每秒鐘調用
tick()
方法。 在其中,Clock
組件通過使用包含當前時間的對象調用setState()
來調度UI更新。 通過調用setState()
,React 知道狀態已經改變,並再次調用render()
方法來確定屏幕上應當顯示什麼。 這一次,render()
方法中的this.state.date
將不同,所以渲染輸出將包含更新的時間,並相應地更新DOM。一旦
Clock
組件被從DOM中移除,React會調用componentWillUnmount()
這個鉤子函數,定時器也就會被清除。
數據自頂向下流動
父組件或子組件都不能知道某個組件是有狀態還是無狀態,並且它們不應該關心某組件是被定義爲一個函數還是一個類。
這就是爲什麼狀態通常被稱爲局部或封裝。 除了擁有並設置它的組件外,其它組件不可訪問。
function FormattedDate(props) { //組件可以選擇將其狀態作爲屬性傳遞給其子組件
return <h2>It is {props.date.toLocaleTimeString()}.</h2>; //<FormattedDate date={this.state.date} />
}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<FormattedDate date={this.state.date} /> {/*子組件接收當前組件的state,FormattedDate 組件將在其屬性中接收到 date 值,並且不知道它是來自 Clock 狀態、還是來自 Clock 的屬性、亦或手工輸入*/}
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
這通常被稱爲自頂向下
或單向
數據流。 任何狀態始終由某些特定組件所有,並且從該狀態導出的任何數據或 UI 只能影響樹中下方
的組件。
如果你想象一個組件樹作爲屬性的瀑布,每個組件的狀態就像一個額外的水源,它連接在一個任意點,但也流下來。
爲了表明所有組件都是真正隔離的,我們可以創建一個 App
組件,它渲染三個Clock
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<FormattedDate date={this.state.date} />
</div>
);
}
}
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));