1目木、什么是react
React.js 是一個(gè)幫助你構(gòu)建頁(yè)面 UI 的庫(kù)。
React.js 將幫助我們將界面分成了各個(gè)獨(dú)立的小塊,每一個(gè)塊就是組件辜羊,這些組件之間可以組合、嵌套词顾,就成了我們的頁(yè)面八秃。
React.js 中一切皆組件,用 React.js 寫(xiě)的其實(shí)就是 React.js 組件肉盹。
React.js 不是一個(gè)框架昔驱,它只是一個(gè)庫(kù)。它只提供 UI (view)層面的解決方案上忍。在實(shí)際的項(xiàng)目當(dāng)中骤肛,它并不能解決我們所有的問(wèn)題,需要結(jié)合其它的庫(kù)窍蓝,例如 Redux腋颠、React-router 等來(lái)協(xié)助提供完整的解決方法。
2吓笙、理解JSX
React.js 就把 JavaScript 的語(yǔ)法擴(kuò)展了一下淑玫,讓 JavaScript 語(yǔ)言能夠支持這種直接在 JavaScript 代碼里面編寫(xiě)類(lèi)似 HTML 標(biāo)簽結(jié)構(gòu)的語(yǔ)法,這樣寫(xiě)起來(lái)就方便很多了。編譯的過(guò)程會(huì)把類(lèi)似 HTML 的 JSX 結(jié)構(gòu)轉(zhuǎn)換成 JavaScript 的對(duì)象結(jié)構(gòu)絮蒿。
所謂的 JSX 其實(shí)就是 JavaScript 對(duì)象尊搬。
class Header extends Component {
render () {
return (
React.createElement(
"div",
null,
React.createElement(
"h1",
{ className: 'title' },
"React 小書(shū)"
)
)
)
}
有些同學(xué)可能會(huì)問(wèn),為什么不直接從 JSX 直接渲染構(gòu)造 DOM 結(jié)構(gòu)土涝,而是要經(jīng)過(guò)中間這么一層呢佛寿?
第一個(gè)原因是,當(dāng)我們拿到一個(gè)表示 UI 的結(jié)構(gòu)和信息的對(duì)象以后回铛,不一定會(huì)把元素渲染到瀏覽器的普通頁(yè)面上,我們有可能把這個(gè)結(jié)構(gòu)渲染到 canvas 上茵肃,或者是手機(jī) App 上。所以這也是為什么會(huì)要把 react-dom 單獨(dú)抽離出來(lái)的原因捞附,可以想象有一個(gè)叫 react-canvas 可以幫我們把 UI 渲染到 canvas 上您没,或者是有一個(gè)叫 react-app 可以幫我們把它轉(zhuǎn)換成原生的 App(實(shí)際上這玩意叫 ReactNative)氨鹏。
第二個(gè)原因是欧募,有了這樣一個(gè)對(duì)象。當(dāng)數(shù)據(jù)變化仆抵,需要更新組件的時(shí)候跟继,就可以用比較快的算法操作這個(gè) JavaScript 對(duì)象,而不用直接操作頁(yè)面上的 DOM镣丑,這樣可以盡量少的減少瀏覽器重排舔糖,極大地優(yōu)化性能。這個(gè)在以后的章節(jié)中我們會(huì)提到莺匠。
總結(jié)
要記住幾個(gè)點(diǎn):
- JSX 是 JavaScript 語(yǔ)言的一種語(yǔ)法擴(kuò)展金吗,長(zhǎng)得像 HTML,但并不是 HTML趣竣。
- React.js 可以用 JSX 來(lái)描述你的組件長(zhǎng)什么樣的摇庙。
- JSX 在編譯的時(shí)候會(huì)變成相應(yīng)的 JavaScript 對(duì)象描述。
- react-dom 負(fù)責(zé)把這個(gè)用來(lái)描述 UI 信息的 JavaScript 對(duì)象變成 DOM 元素期贫,并且渲染到頁(yè)面上跟匆。
3、組件的組合通砍、嵌套和組件樹(shù)
組件可以和組件組合在一起玛臂,組件內(nèi)部可以使用別的組件烤蜕。就像普通的 HTML 標(biāo)簽一樣使用就可以。這樣的組合嵌套迹冤,最后構(gòu)成一個(gè)所謂的組件樹(shù)讽营,就正如上面的例子那樣,Index 用了 Header泡徙、Main橱鹏、Footer,Header 又使用了 Title 堪藐。這樣用這樣的樹(shù)狀結(jié)構(gòu)表示它們之間的關(guān)系:
當(dāng)頁(yè)面結(jié)構(gòu)復(fù)雜起來(lái)莉兰,有許多不同的組件嵌套組合的話(huà),組件樹(shù)會(huì)相當(dāng)?shù)膹?fù)雜和龐大礁竞。理解組件樹(shù)的概念對(duì)后面理解數(shù)據(jù)是如何在組件樹(shù)內(nèi)自上往下流動(dòng)過(guò)程很重要糖荒。
4、事件監(jiān)聽(tīng)
關(guān)于事件中的 this
一般在某個(gè)類(lèi)的實(shí)例方法里面的 this 指的是這個(gè)實(shí)例本身模捂。
但是 React.js 調(diào)用你所傳給它的方法的時(shí)候捶朵,并不是通過(guò)對(duì)象方法的方式調(diào)用(this.handleClickOnTitle),而是直接通過(guò)函數(shù)調(diào)用 (handleClickOnTitle)狂男,所以事件監(jiān)聽(tīng)函數(shù)內(nèi)并不能通過(guò) this 獲取到實(shí)例综看。
如果你想在事件函數(shù)當(dāng)中使用當(dāng)前的實(shí)例,你需要手動(dòng)地將實(shí)例方法 bind 到當(dāng)前實(shí)例上再傳入給 React.js岖食。
class Title extends Component {
handleClickOnTitle (e) {
console.log(this)
}
render () {
return (
<h1 onClick={this.handleClickOnTitle.bind(this)}>React 小書(shū)</h1>
)
}
}
總結(jié)
為 React 的組件添加事件監(jiān)聽(tīng)是很簡(jiǎn)單的事情红碑,你只需要使用 React.js 提供了一系列的 on* 方法即可。
React.js 會(huì)給每個(gè)事件監(jiān)聽(tīng)傳入一個(gè) event 對(duì)象泡垃,這個(gè)對(duì)象提供的功能和瀏覽器提供的功能一致句喷,而且它是兼容所有瀏覽器的。
React.js 的事件監(jiān)聽(tīng)方法需要手動(dòng) bind 到當(dāng)前實(shí)例兔毙,這種模式在 React.js 中非常常用。
5兄春、組件的 state 和 setState
setState 接受函數(shù)參數(shù):
這里還有要注意的是澎剥,當(dāng)你調(diào)用 setState 的時(shí)候,React.js 并不會(huì)馬上修改 state赶舆。而是把這個(gè)對(duì)象放到一個(gè)更新隊(duì)列里面哑姚,稍后才會(huì)從隊(duì)列當(dāng)中把新的狀態(tài)提取出來(lái)合并到 state 當(dāng)中,然后再觸發(fā)組件更新芜茵。這一點(diǎn)要好好注意叙量。可以體會(huì)一下下面的代碼:
handleClickOnLikeButton () {
console.log(this.state.isLiked)
this.setState({
isLiked: !this.state.isLiked
})
console.log(this.state.isLiked)
}
你會(huì)發(fā)現(xiàn)兩次打印的都是 false九串,即使我們中間已經(jīng) setState 過(guò)一次了绞佩。這并不是什么 bug寺鸥,只是 React.js 的 setState 把你的傳進(jìn)來(lái)的狀態(tài)緩存起來(lái),稍后才會(huì)幫你更新到 state 上品山,所以你獲取到的還是原來(lái)的 isLiked胆建。
所以如果你想在 setState 之后使用新的 state 來(lái)做后續(xù)運(yùn)算就做不到了,例如:
handleClickOnLikeButton () {
this.setState({ count: 0 }) // => this.state.count 還是 undefined
this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN
this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN
}
上面的代碼的運(yùn)行結(jié)果并不能達(dá)到我們的預(yù)期肘交,我們希望 count 運(yùn)行結(jié)果是 3 笆载,可是最后得到的是 NaN。但是這種后續(xù)操作依賴(lài)前一個(gè) setState 的結(jié)果的情況并不罕見(jiàn)涯呻。
這里就自然地引出了setState 的第二種使用方式:可以接受一個(gè)函數(shù)作為參數(shù)凉驻,。React.js 會(huì)把上一個(gè) setState 的結(jié)果傳入這個(gè)函數(shù)复罐,你就可以使用該結(jié)果進(jìn)行運(yùn)算涝登、操作,然后返回一個(gè)對(duì)象作為更新 state 的對(duì)象:
handleClickOnLikeButton () {
this.setState((prevState) => {
return { count: 0 }
})
this.setState((prevState) => {
return { count: prevState.count + 1 } // 上一個(gè) setState 的返回是 count 為 0市栗,當(dāng)前返回 1
})
this.setState((prevState) => {
return { count: prevState.count + 2 } // 上一個(gè) setState 的返回是 count 為 1缀拭,當(dāng)前返回 3
})
// 最后的結(jié)果是 this.state.count 為 3
}
這樣就可以達(dá)到上述的利用上一次 setState 結(jié)果進(jìn)行運(yùn)算的效果。
setState 合并
上面我們進(jìn)行了三次 setState填帽,但是實(shí)際上組件只會(huì)重新渲染一次蛛淋,而不是三次;這是因?yàn)樵?React.js 內(nèi)部會(huì)把 JavaScript 事件循環(huán)中的消息隊(duì)列的同一個(gè)消息中的 setState 都進(jìn)行合并以后再重新渲染組件篡腌。
深層的原理并不需要過(guò)多糾結(jié)褐荷,你只需要記住的是:在使用 React.js 的時(shí)候,并不需要擔(dān)心多次進(jìn)行 setState 會(huì)帶來(lái)性能問(wèn)題嘹悼。
6叛甫、配置組件的 props
在使用一個(gè)組件的時(shí)候,可以把參數(shù)放在標(biāo)簽的屬性當(dāng)中杨伙,所有的屬性都會(huì)作為 props 對(duì)象的鍵值:
class LikeButton extends Component {
constructor () {
...
}
handleClickOnLikeButton () {
...
}
render () {
const wordings = this.props.wordings || { //獲取props
likedText: '取消',
unlikedText: '點(diǎn)贊'
}
return (
<button onClick={this.handleClickOnLikeButton.bind(this)}>
{this.state.isLiked ? wordings.likedText : wordings.unlikedText} ??
</button>
)
}
}
---------
class Index extends Component {
render () {
return (
<div>
<LikeButton likedText='已贊' unlikedText='贊' />
</div>
)
}
}
props 不可變
props 一旦傳入進(jìn)來(lái)就不能改變其监。修改上面的例子中的 handleClickOnLikeButton:
handleClickOnLikeButton () {
this.props.likedText = '取消'
this.setState({
isLiked: !this.state.isLiked
})
}
我們嘗試在用戶(hù)點(diǎn)擊按鈕的時(shí)候改變 this.props.likedText
,然后你會(huì)看到控制臺(tái)報(bào)錯(cuò)了限匣。
你不能改變一個(gè)組件被渲染的時(shí)候傳進(jìn)來(lái)的 props抖苦。React.js 希望一個(gè)組件在輸入確定的 props 的時(shí)候,能夠輸出確定的 UI 顯示形態(tài)米死。如果 props 渲染過(guò)程中可以被修改锌历,那么就會(huì)導(dǎo)致這個(gè)組件顯示形態(tài)和行為變得不可預(yù)測(cè),這樣會(huì)可能會(huì)給組件使用者帶來(lái)困惑峦筒。
但這并不意味著由 props 決定的顯示形態(tài)不能被修改究西。組件的使用者可以主動(dòng)地通過(guò)重新渲染的方式把新的 props 傳入組件當(dāng)中,這樣這個(gè)組件中由 props 決定的顯示形態(tài)也會(huì)得到相應(yīng)的改變物喷。
總結(jié)
- 為了使得組件的可定制性更強(qiáng)卤材,在使用組件的時(shí)候遮斥,可以在標(biāo)簽上加屬性來(lái)傳入配置參數(shù)。
- 組件可以在內(nèi)部通過(guò) this.props 獲取到配置參數(shù)商膊,組件可以根據(jù) props 的不同來(lái)確定自己的顯示形態(tài)伏伐,達(dá)到可配置的效果。
- 可以通過(guò)給組件添加類(lèi)屬性 defaultProps 來(lái)配置默認(rèn)參數(shù)晕拆。
props 一旦傳入藐翎,你就不可以在組件內(nèi)部對(duì)它進(jìn)行修改。但是你可以通過(guò)父組件主動(dòng)重新渲染的方式來(lái)傳入新的 props实幕,從而達(dá)到更新的效果吝镣。
7、state vs props
我們來(lái)一個(gè)關(guān)于 state 和 props 的總結(jié)昆庇。
state:主要作用是用于組件保存末贾、控制、修改自己的可變狀態(tài)整吆。state 在組件內(nèi)部初始化拱撵,可以被組件自身修改,而外部不能訪(fǎng)問(wèn)也不能修改表蝙。你可以認(rèn)為 state 是一個(gè)局部的拴测、只能被組件自身控制的數(shù)據(jù)源。state 中狀態(tài)可以通過(guò) this.setState 方法進(jìn)行更新府蛇,setState 會(huì)導(dǎo)致組件的重新渲染集索。
props: 的主要作用是讓使用該組件的父組件可以傳入?yún)?shù)來(lái)配置該組件。它是外部傳進(jìn)來(lái)的配置參數(shù)汇跨,組件內(nèi)部無(wú)法控制也無(wú)法修改务荆。除非外部組件主動(dòng)傳入新的 props,否則組件的 props 永遠(yuǎn)保持不變穷遂。
state 和 props 有著千絲萬(wàn)縷的關(guān)系函匕。它們都可以決定組件的行為和顯示形態(tài)。一個(gè)組件的 state 中的數(shù)據(jù)可以通過(guò) props 傳給子組件蚪黑,一個(gè)組件可以使用外部傳入的 props 來(lái)初始化自己的 state浦箱。但是它們的職責(zé)其實(shí)非常明晰分明:state 是讓組件控制自己的狀態(tài),props 是讓外部對(duì)組件自己進(jìn)行配置祠锣。
如果你覺(jué)得還是搞不清 state 和 props 的使用場(chǎng)景,那么請(qǐng)記住一個(gè)簡(jiǎn)單的規(guī)則:盡量少地用 state咽安,盡量多地用 props伴网。
沒(méi)有 state 的組件叫無(wú)狀態(tài)組件(stateless component),設(shè)置了 state 的叫做有狀態(tài)組件(stateful component)妆棒。
因?yàn)闋顟B(tài)會(huì)帶來(lái)管理的復(fù)雜性澡腾,我們盡量多地寫(xiě)無(wú)狀態(tài)組件沸伏,盡量少地寫(xiě)有狀態(tài)的組件。這樣會(huì)降低代碼維護(hù)的難度动分,也會(huì)在一定程度上增強(qiáng)組件的可復(fù)用性毅糟。前端應(yīng)用狀態(tài)管理是一個(gè)復(fù)雜的問(wèn)題,我們后續(xù)會(huì)繼續(xù)討論澜公。
React.js 非常鼓勵(lì)無(wú)狀態(tài)組件姆另,在 0.14 版本引入了函數(shù)式組件——一種定義不能使用 state 組件,例如一個(gè)原來(lái)這樣寫(xiě)的組件:
class HelloWorld extends Component {
constructor() {
super()
}
sayHi () {
alert('Hello World')
}
render () {
return (
<div onClick={this.sayHi.bind(this)}>Hello World</div>
)
}
}
用函數(shù)式組件的編寫(xiě)方式就是:
const HelloWorld = (props) => {
const sayHi = (event) => alert('Hello World')
return (
<div onClick={sayHi}>Hello World</div>
)
}
以前一個(gè)組件是通過(guò)繼承 Component 來(lái)構(gòu)建坟乾,一個(gè)子類(lèi)就是一個(gè)組件迹辐。而用函數(shù)式的組件編寫(xiě)方式是一個(gè)函數(shù)就是一個(gè)組件,你可以和以前一樣通過(guò) <HellWorld /> 使用該組件甚侣。不同的是明吩,函數(shù)式組件只能接受 props 而無(wú)法像跟類(lèi)組件一樣可以在 constructor 里面初始化 state。你可以理解函數(shù)式組件就是一種只能接受 props 和提供 render 方法的類(lèi)組件殷费。
8印荔、渲染列表數(shù)據(jù)
列表數(shù)據(jù)在前端非常常見(jiàn),我們經(jīng)常要處理這種類(lèi)型的數(shù)據(jù)详羡,例如文章列表仍律、評(píng)論列表、用戶(hù)列表…一個(gè)前端工程師幾乎每天都需要跟列表數(shù)據(jù)打交道殷绍。
React.js 當(dāng)然也允許我們處理列表數(shù)據(jù)染苛,但在使用 React.js 處理列表數(shù)據(jù)的時(shí)候,需要掌握一些規(guī)則主到。我們這一節(jié)會(huì)專(zhuān)門(mén)討論這方面的知識(shí)茶行。
渲染存放 JSX 元素的數(shù)組
假設(shè)現(xiàn)在我們有這么一個(gè)用戶(hù)列表數(shù)據(jù),存放在一個(gè)數(shù)組當(dāng)中:
const users = [
{ username: 'Jerry', age: 21, gender: 'male' },
{ username: 'Tomy', age: 22, gender: 'male' },
{ username: 'Lily', age: 19, gender: 'female' },
{ username: 'Lucy', age: 20, gender: 'female' }
]
如果現(xiàn)在要把這個(gè)數(shù)組里面的數(shù)據(jù)渲染頁(yè)面上要怎么做登钥?開(kāi)始之前要補(bǔ)充一個(gè)知識(shí)畔师。之前說(shuō)過(guò) JSX 的表達(dá)式插入 {}
里面可以放任何數(shù)據(jù),如果我們往 {}
里面放一個(gè)存放 JSX 元素的數(shù)組會(huì)怎么樣牧牢?
...
class Index extends Component {
render () {
return (
<div>
{[
<span>React.js </span>,
<span>is </span>,
<span>good</span>
]}
</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('root')
)
我們往 JSX 里面塞了一個(gè)數(shù)組看锉,這個(gè)數(shù)組里面放了一些 JSX 元素(其實(shí)就是 JavaScript 對(duì)象)。到瀏覽器中塔鳍,你在頁(yè)面上會(huì)看到:
[圖片上傳失敗...(image-dfbb5e-1530370683811)]
審查一下元素伯铣,看看會(huì)發(fā)現(xiàn)什么:
React.js 把插入表達(dá)式數(shù)組里面的每一個(gè) JSX 元素一個(gè)個(gè)羅列下來(lái),渲染到頁(yè)面上轮纫。所以這里有個(gè)關(guān)鍵點(diǎn):如果你往 {}
放一個(gè)數(shù)組腔寡,React.js 會(huì)幫你把數(shù)組里面一個(gè)個(gè)元素羅列并且渲染出來(lái)。
使用 map 渲染列表數(shù)據(jù)
知道這一點(diǎn)以后你就可以知道怎么用循環(huán)把元素渲染到頁(yè)面上:循環(huán)上面用戶(hù)數(shù)組里面的每一個(gè)用戶(hù)掌唾,為每個(gè)用戶(hù)數(shù)據(jù)構(gòu)建一個(gè) JSX放前,然后把 JSX 放到一個(gè)新的數(shù)組里面忿磅,再把新的數(shù)組插入 render
方法的 JSX 里面∑居铮看看代碼怎么寫(xiě):
const users = [
{ username: 'Jerry', age: 21, gender: 'male' },
{ username: 'Tomy', age: 22, gender: 'male' },
{ username: 'Lily', age: 19, gender: 'female' },
{ username: 'Lucy', age: 20, gender: 'female' }
]
class Index extends Component {
render () {
const usersElements = [] // 保存每個(gè)用戶(hù)渲染以后 JSX 的數(shù)組
for (let user of users) {
usersElements.push( // 循環(huán)每個(gè)用戶(hù)葱她,構(gòu)建 JSX,push 到數(shù)組中
<div>
<div>姓名:{user.username}</div>
<div>年齡:{user.age}</div>
<div>性別:{user.gender}</div>
<hr />
</div>
)
}
return (
<div>{usersElements}</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('root')
)
這里用了一個(gè)新的數(shù)組 usersElements
似扔,然后循環(huán) users
數(shù)組吨些,為每個(gè) user
構(gòu)建一個(gè) JSX 結(jié)構(gòu),然后 push 到 usersElements
中虫几。然后直接用表達(dá)式插入锤灿,把這個(gè) userElements
插到 return 的 JSX 當(dāng)中。因?yàn)?React.js 會(huì)自動(dòng)化幫我們把數(shù)組當(dāng)中的 JSX 羅列渲染出來(lái)辆脸,所以可以看到頁(yè)面上顯示:
但我們一般不會(huì)手動(dòng)寫(xiě)循環(huán)來(lái)構(gòu)建列表的 JSX 結(jié)構(gòu)但校,可以直接用 ES6 自帶的 map
(不了解 map
函數(shù)的同學(xué)可以先了解相關(guān)的知識(shí)再來(lái)回顧這里),代碼可以簡(jiǎn)化成:
class Index extends Component {
render () {
return (
<div>
{users.map((user) => {
return (
<div>
<div>姓名:{user.username}</div>
<div>年齡:{user.age}</div>
<div>性別:{user.gender}</div>
<hr />
</div>
)
})}
</div>
)
}
}
這樣的模式在 JavaScript 中非常常見(jiàn)啡氢,一般來(lái)說(shuō)状囱,在 React.js 處理列表就是用 map
來(lái)處理、渲染的√仁牵現(xiàn)在進(jìn)一步把渲染單獨(dú)一個(gè)用戶(hù)的結(jié)構(gòu)抽離出來(lái)作為一個(gè)組件亭枷,繼續(xù)優(yōu)化代碼:
const users = [
{ username: 'Jerry', age: 21, gender: 'male' },
{ username: 'Tomy', age: 22, gender: 'male' },
{ username: 'Lily', age: 19, gender: 'female' },
{ username: 'Lucy', age: 20, gender: 'female' }
]
class User extends Component {
render () {
const { user } = this.props
return (
<div>
<div>姓名:{user.username}</div>
<div>年齡:{user.age}</div>
<div>性別:{user.gender}</div>
<hr />
</div>
)
}
}
class Index extends Component {
render () {
return (
<div>
{users.map((user) => <User user={user} />)}
</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('root')
)
這里把負(fù)責(zé)展示用戶(hù)數(shù)據(jù)的 JSX 結(jié)構(gòu)抽離成一個(gè)組件 User
,并且通過(guò) props
把 user
數(shù)據(jù)作為組件的配置參數(shù)傳進(jìn)去搀崭;這樣改寫(xiě) Index
就非常清晰了叨粘,看一眼就知道負(fù)責(zé)渲染 users
列表,而用的組件是 User
瘤睹。
key! key! key!
現(xiàn)在代碼運(yùn)作正常升敲,好像沒(méi)什么問(wèn)題。打開(kāi)控制臺(tái)看看:
React.js 報(bào)錯(cuò)了轰传。如果需要詳細(xì)解釋這里報(bào)錯(cuò)的原因驴党,估計(jì)要單獨(dú)寫(xiě)半本書(shū)。但可以簡(jiǎn)單解釋一下获茬。
React.js 的是非常高效的港庄,它高效依賴(lài)于所謂的 Virtual-DOM 策略。簡(jiǎn)單來(lái)說(shuō)恕曲,能復(fù)用的話(huà) React.js 就會(huì)盡量復(fù)用鹏氧,沒(méi)有必要的話(huà)絕對(duì)不碰 DOM。對(duì)于列表元素來(lái)說(shuō)也是這樣佩谣,但是處理列表元素的復(fù)用性會(huì)有一個(gè)問(wèn)題:元素可能會(huì)在一個(gè)列表中改變位置度帮。例如:
<div>a</div>
<div>b</div>
<div>c</div>
假設(shè)頁(yè)面上有這么3個(gè)列表元素,現(xiàn)在改變一下位置:
<div>a</div>
<div>c</div>
<div>b</div>
c
和 b
的位置互換了。但其實(shí) React.js 只需要交換一下 DOM 位置就行了笨篷,但是它并不知道其實(shí)我們只是改變了元素的位置,所以它會(huì)重新渲染后面兩個(gè)元素(再執(zhí)行 Virtual-DOM 策略)瓣履,這樣會(huì)大大增加 DOM 操作率翅。但如果給每個(gè)元素加上唯一的標(biāo)識(shí),React.js 就可以知道這兩個(gè)元素只是交換了位置:
<div key='a'>a</div>
<div key='b'>b</div>
<div key='c'>c</div>
這樣 React.js 就簡(jiǎn)單的通過(guò) key
來(lái)判斷出來(lái)袖迎,這兩個(gè)列表元素只是交換了位置冕臭,可以盡量復(fù)用元素內(nèi)部的結(jié)構(gòu)。
這里沒(méi)聽(tīng)懂沒(méi)有關(guān)系燕锥,后面有機(jī)會(huì)會(huì)繼續(xù)講解這部分內(nèi)容」脊螅現(xiàn)在只需要記住一個(gè)簡(jiǎn)單的規(guī)則:對(duì)于用表達(dá)式套數(shù)組羅列到頁(yè)面上的元素,都要為每個(gè)元素加上 key
屬性归形,這個(gè) key
必須是每個(gè)元素唯一的標(biāo)識(shí)托慨。一般來(lái)說(shuō),key
的值可以直接后臺(tái)數(shù)據(jù)返回的 id
暇榴,因?yàn)楹笈_(tái)的 id
都是唯一的厚棵。
在上面的例子當(dāng)中,每個(gè) user
沒(méi)有 id
可以用蔼紧,可以直接用循環(huán)計(jì)數(shù)器 i
作為 key
:
...
class Index extends Component {
render () {
return (
<div>
{users.map((user, i) => <User key={i} user={user} />)}
</div>
)
}
}
...
再看看婆硬,控制臺(tái)已經(jīng)沒(méi)有錯(cuò)誤信息了。但這是不好的做法奸例,這只是掩耳盜鈴(具體原因大家可以自己思考一下)彬犯。記住一點(diǎn):在實(shí)際項(xiàng)目當(dāng)中,如果你的數(shù)據(jù)順序可能發(fā)生變化查吊,標(biāo)準(zhǔn)做法是最好是后臺(tái)數(shù)據(jù)返回的 id
作為列表元素的 key
谐区。
9、前端應(yīng)用狀態(tài)管理 —— 狀態(tài)提升
我們?cè)?a target="_blank" rel="nofollow">講解 JSX 的章節(jié)中提到菩貌,下面的代碼:
ReactDOM.render(
<Header />,
document.getElementById('root')
)
會(huì)編譯成:
ReactDOM.render(
React.createElement(Header, null),
document.getElementById('root')
)
其實(shí)我們把 Header
組件傳給了 React.createElement
函數(shù)卢佣,又把函數(shù)返回結(jié)果傳給了 ReactDOM.render
。我們可以簡(jiǎn)單猜想一下它們會(huì)干什么事情:
// React.createElement 中實(shí)例化一個(gè) Header
const header = new Header(props, children)
// React.createElement 中調(diào)用 header.render 方法渲染組件的內(nèi)容
const headerJsxObject = header.render()
// ReactDOM 用渲染后的 JavaScript 對(duì)象來(lái)來(lái)構(gòu)建真正的 DOM 元素
const headerDOM = createDOMFromObject(headerJsxObject)
// ReactDOM 把 DOM 元素塞到頁(yè)面上
document.getElementById('root').appendChild(headerDOM)
上面過(guò)程其實(shí)很簡(jiǎn)單箭阶,看代碼就能理解虚茶。
我們把 React.js 將組件渲染,并且構(gòu)造 DOM 元素然后塞入頁(yè)面的過(guò)程稱(chēng)為組件的掛載(這個(gè)定義請(qǐng)好好記壮鸩巍)嘹叫。其實(shí) React.js 內(nèi)部對(duì)待每個(gè)組件都有這么一個(gè)過(guò)程,也就是初始化組件 -> 掛載到頁(yè)面上的過(guò)程诈乒。所以你可以理解一個(gè)組件的方法調(diào)用是這么一個(gè)過(guò)程:
-> constructor()
-> render()
// 然后構(gòu)造 DOM 元素插入頁(yè)面
這當(dāng)然是很好理解的罩扇。React.js 為了讓我們能夠更好的掌控組件的掛載過(guò)程,往上面插入了兩個(gè)方法:
-> constructor()
-> componentWillMount()
-> render()
// 然后構(gòu)造 DOM 元素插入頁(yè)面
-> componentDidMount()
componentWillMount
和 componentDidMount
都是可以像 render
方法一樣自定義在組件的內(nèi)部。掛載的時(shí)候喂饥,React.js 會(huì)在組件的 render
之前調(diào)用 componentWillMount
消约,在 DOM 元素塞入頁(yè)面以后調(diào)用 componentDidMount
。
我們給 Header
組件加上這兩個(gè)方法员帮,并且打一些 Log:
class Header extends Component {
constructor () {
super()
console.log('construct')
}
componentWillMount () {
console.log('component will mount')
}
componentDidMount () {
console.log('component did mount')
}
render () {
console.log('render')
return (
<div>
<h1 className='title'>React 小書(shū)</h1>
</div>
)
}
}
在控制臺(tái)你可以看到依次輸出:
可以看到或粮,React.js 確實(shí)按照我們上面所說(shuō)的那樣調(diào)用了定義的兩個(gè)方法 componentWillMount
和 componentDidMount
。
機(jī)靈的同學(xué)可以想到捞高,一個(gè)組件可以插入頁(yè)面氯材,當(dāng)然也可以從頁(yè)面中刪除。
-> constructor()
-> componentWillMount()
-> render()
// 然后構(gòu)造 DOM 元素插入頁(yè)面
-> componentDidMount()
// ...
// 從頁(yè)面中刪除
React.js 也控制了這個(gè)組件的刪除過(guò)程硝岗。在組件刪除之前 React.js 會(huì)調(diào)用組件定義的 componentWillUnmount
:
-> constructor()
-> componentWillMount()
-> render()
// 然后構(gòu)造 DOM 元素插入頁(yè)面
-> componentDidMount()
// ...
// 即將從頁(yè)面中刪除
-> componentWillUnmount()
// 從頁(yè)面中刪除
看看什么情況下會(huì)把組件從頁(yè)面中刪除氢哮,繼續(xù)使用上面例子的代碼,我們?cè)俣x一個(gè) Index
組件:
class Index extends Component {
constructor() {
super()
this.state = {
isShowHeader: true
}
}
handleShowOrHide () {
this.setState({
isShowHeader: !this.state.isShowHeader
})
}
render () {
return (
<div>
{this.state.isShowHeader ? <Header /> : null}
<button onClick={this.handleShowOrHide.bind(this)}>
顯示或者隱藏標(biāo)題
</button>
</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('root')
)
Index
組件使用了 Header
組件型檀,并且有一個(gè)按鈕冗尤,可以控制 Header
的顯示或者隱藏。下面這行代碼:
...a
{this.state.isShowHeader ? <Header /> : null}
...
相當(dāng)于 state.isShowHeader
為 true
的時(shí)候把 Header
插入頁(yè)面贱除,false
的時(shí)候把 Header
從頁(yè)面上刪除生闲。這時(shí)候我們給 Header
添加 componentWillUnmount
方法:
...
componentWillUnmount() {
console.log('component will unmount')
}
...
這時(shí)候點(diǎn)擊頁(yè)面上的按鈕,你會(huì)看到頁(yè)面的標(biāo)題隱藏了月幌,并且控制臺(tái)打印出來(lái)下圖的最后一行碍讯,說(shuō)明 componentWillUnmount
確實(shí)被 React.js 所調(diào)用了:
你可以多次點(diǎn)擊按鈕,隨著按鈕的顯示和隱藏扯躺,上面的內(nèi)容會(huì)按順序重復(fù)地打印出來(lái)捉兴,可以體會(huì)一下這幾個(gè)方法的調(diào)用過(guò)程和順序。
總結(jié)
React.js 將組件渲染录语,并且構(gòu)造 DOM 元素然后塞入頁(yè)面的過(guò)程稱(chēng)為組件的掛載倍啥。這一節(jié)我們學(xué)習(xí)了 React.js 控制組件在頁(yè)面上掛載和刪除過(guò)程里面幾個(gè)方法:
-
componentWillMount
:組件掛載開(kāi)始之前,也就是在組件調(diào)用render
方法之前調(diào)用澎埠。 -
componentDidMount
:組件掛載完成以后虽缕,也就是 DOM 元素已經(jīng)插入頁(yè)面后調(diào)用。 -
componentWillUnmount
:組件對(duì)應(yīng)的 DOM 元素從頁(yè)面中刪除之前調(diào)用蒲稳。
但這一節(jié)并沒(méi)有講這幾個(gè)方法到底在實(shí)際項(xiàng)目當(dāng)中有什么作用氮趋,下一節(jié)我們通過(guò)例子來(lái)講解一下這幾個(gè)方法的用途。
10江耀、掛載階段的組件生命周期(二)
這一節(jié)我們來(lái)討論一下對(duì)于一個(gè)組件來(lái)說(shuō)剩胁,constructor
、componentWillMount
祥国、componentDidMount
昵观、componentWillUnmount
這幾個(gè)方法在一個(gè)組件的出生到死亡的過(guò)程里面起了什么樣的作用。
一般來(lái)說(shuō),所有關(guān)于組件自身的狀態(tài)的初始化工作都會(huì)放在 constructor
里面去做啊犬。你會(huì)發(fā)現(xiàn)本書(shū)所有組件的 state
的初始化工作都是放在 constructor
里面的灼擂。假設(shè)我們現(xiàn)在在做一個(gè)時(shí)鐘應(yīng)用:
我們會(huì)在 constructor
里面初始化 state.date
,當(dāng)然現(xiàn)在頁(yè)面還是靜態(tài)的觉至,等下一會(huì)讓時(shí)間動(dòng)起來(lái)缤至。
class Clock extends Component {
constructor () {
super()
this.state = {
date: new Date()
}
}
render () {
return (
<div>
<h1>
<p>現(xiàn)在的時(shí)間是</p>
{this.state.date.toLocaleTimeString()}
</h1>
</div>
)
}
}
一些組件啟動(dòng)的動(dòng)作,包括像 Ajax 數(shù)據(jù)的拉取操作康谆、一些定時(shí)器的啟動(dòng)等,就可以放在 componentWillMount
里面進(jìn)行嫉到,例如 Ajax:
...
componentWillMount () {
ajax.get('http://json-api.com/user', (userData) => {
this.setState({ userData })
})
}
...
當(dāng)然在我們這個(gè)例子里面是定時(shí)器的啟動(dòng)沃暗,我們給 Clock
啟動(dòng)定時(shí)器:
class Clock extends Component {
constructor () {
super()
this.state = {
date: new Date()
}
}
componentWillMount () {
this.timer = setInterval(() => {
this.setState({ date: new Date() })
}, 1000)
}
...
}
我們?cè)?componentWillMount
中用 setInterval
啟動(dòng)了一個(gè)定時(shí)器:每隔 1 秒更新中的 state.date
,這樣頁(yè)面就可以動(dòng)起來(lái)了何恶。我們用一個(gè) Index
把它用起來(lái)孽锥,并且插入頁(yè)面:
class Index extends Component {
render () {
return (
<div>
<Clock />
</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('root')
)
像上一節(jié)那樣,我們修改這個(gè) Index
讓這個(gè)時(shí)鐘可以隱藏或者顯示:
class Index extends Component {
constructor () {
super()
this.state = { isShowClock: true }
}
handleShowOrHide () {
this.setState({
isShowClock: !this.state.isShowClock
})
}
render () {
return (
<div>
{this.state.isShowClock ? <Clock /> : null }
<button onClick={this.handleShowOrHide.bind(this)}>
顯示或隱藏時(shí)鐘
</button>
</div>
)
}
}
現(xiàn)在頁(yè)面上有個(gè)按鈕可以顯示或者隱藏時(shí)鐘细层。你試一下顯示或者隱藏時(shí)鐘惜辑,雖然頁(yè)面上看起來(lái)功能都正常,在控制臺(tái)你會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了:
這是因?yàn)椋?em>當(dāng)時(shí)鐘隱藏的時(shí)候疫赎,我們并沒(méi)有清除定時(shí)器盛撑。時(shí)鐘隱藏的時(shí)候,定時(shí)器的回調(diào)函數(shù)還在不停地嘗試 setState
捧搞,由于 setState
只能在已經(jīng)掛載或者正在掛載的組件上調(diào)用抵卫,所以 React.js 開(kāi)始瘋狂報(bào)錯(cuò)。
多次的隱藏和顯示會(huì)讓 React.js 重新構(gòu)造和銷(xiāo)毀 Clock
組件胎撇,每次構(gòu)造都會(huì)重新構(gòu)建一個(gè)定時(shí)器介粘。而銷(xiāo)毀組件的時(shí)候沒(méi)有清除定時(shí)器,所以你看到報(bào)錯(cuò)會(huì)越來(lái)越多晚树。而且因?yàn)?JavaScript 的閉包特性姻采,這樣會(huì)導(dǎo)致嚴(yán)重的內(nèi)存泄漏。
這時(shí)候componentWillUnmount
就可以派上用場(chǎng)了爵憎,它的作用就是在組件銷(xiāo)毀的時(shí)候慨亲,做這種清場(chǎng)的工作。例如清除該組件的定時(shí)器和其他的數(shù)據(jù)清理工作纲堵。我們給 Clock
添加 componentWillUnmount
巡雨,在組件銷(xiāo)毀的時(shí)候清除該組件的定時(shí)器:
...
componentWillUnmount () {
clearInterval(this.timer)
}
...
這時(shí)候就沒(méi)有錯(cuò)誤了。
總結(jié)
我們一般會(huì)把組件的 state
的初始化工作放在 constructor
里面去做席函;在 componentWillMount
進(jìn)行組件的啟動(dòng)工作铐望,例如 Ajax 數(shù)據(jù)拉取、定時(shí)器的啟動(dòng);組件從頁(yè)面上銷(xiāo)毀的時(shí)候正蛙,有時(shí)候需要一些數(shù)據(jù)的清理督弓,例如定時(shí)器的清理,就會(huì)放在 componentWillUnmount
里面去做乒验。
說(shuō)一下本節(jié)沒(méi)有提到的 componentDidMount
愚隧。一般來(lái)說(shuō),有些組件的啟動(dòng)工作是依賴(lài) DOM 的锻全,例如動(dòng)畫(huà)的啟動(dòng)狂塘,而 componentWillMount
的時(shí)候組件還沒(méi)掛載完成,所以沒(méi)法進(jìn)行這些啟動(dòng)工作鳄厌,這時(shí)候就可以把這些操作放在 componentDidMount
當(dāng)中荞胡。componentDidMount
的具體使用我們會(huì)在接下來(lái)的章節(jié)當(dāng)中結(jié)合 DOM 來(lái)講。
11了嚎、更新階段的組件生命周期
從之前的章節(jié)我們了解到泪漂,組件的掛載指的是將組件渲染并且構(gòu)造 DOM 元素然后插入頁(yè)面的過(guò)程。這是一個(gè)從無(wú)到有的過(guò)程歪泳,React.js 提供一些生命周期函數(shù)可以給我們?cè)谶@個(gè)過(guò)程中做一些操作萝勤。
除了掛載階段,還有一種“更新階段”呐伞。說(shuō)白了就是 setState
導(dǎo)致 React.js 重新渲染組件并且把組件的變化應(yīng)用到 DOM 元素上的過(guò)程敌卓,這是一個(gè)組件的變化過(guò)程。而 React.js 也提供了一系列的生命周期函數(shù)可以讓我們?cè)谶@個(gè)組件更新的過(guò)程執(zhí)行一些操作荸哟。
這些生命周期在深入項(xiàng)目開(kāi)發(fā)階段是非常重要的假哎。而要完全理解更新階段的組件生命周期是一個(gè)需要經(jīng)驗(yàn)和知識(shí)積累的過(guò)程,你需要對(duì) Virtual-DOM 策略有比較深入理解才能完全掌握鞍历,但這超出了本書(shū)的目的舵抹。本書(shū)的目的是為了讓大家快速掌握 React.js 核心的概念,快速上手項(xiàng)目進(jìn)行實(shí)戰(zhàn)劣砍。所以對(duì)于組件更新階段的組件生命周期惧蛹,我們簡(jiǎn)單提及并且提供一些資料給大家。
這里為了知識(shí)的完整刑枝,補(bǔ)充關(guān)于更新階段的組件生命周期:
-
shouldComponentUpdate(nextProps, nextState)
:你可以通過(guò)這個(gè)方法控制組件是否重新渲染香嗓。如果返回false
組件就不會(huì)重新渲染。這個(gè)生命周期在 React.js 性能優(yōu)化上非常有用装畅。 -
componentWillReceiveProps(nextProps)
:組件從父組件接收到新的props
之前調(diào)用靠娱。 -
componentWillUpdate()
:組件開(kāi)始重新渲染之前調(diào)用。 -
componentDidUpdate()
:組件重新渲染并且把更改變更到真實(shí)的 DOM 以后調(diào)用掠兄。
大家對(duì)這更新階段的生命周期比較感興趣的話(huà)可以查看官網(wǎng)文檔像云。
但這里建議大家可以先簡(jiǎn)單了解 React.js 組件是有更新階段的锌雀,并且有這么幾個(gè)更新階段的生命周期即可。然后在深入項(xiàng)目實(shí)戰(zhàn)的時(shí)候逐漸地掌握理解他們迅诬,現(xiàn)在并不需要對(duì)他們放過(guò)多的精力腋逆。
12、ref 和 React.js 中的 DOM 操作
在 React.js 當(dāng)中你基本不需要和 DOM 直接打交道侈贷。React.js 提供了一系列的 on* 方法幫助我們進(jìn)行事件監(jiān)聽(tīng)惩歉,所以 React.js 當(dāng)中不需要直接調(diào)用 addEventListener 的 DOM API;以前我們通過(guò)手動(dòng) DOM 操作進(jìn)行頁(yè)面更新(例如借助 jQuery),而在 React.js 當(dāng)中可以直接通過(guò) setState 的方式重新渲染組件,渲染的時(shí)候可以把新的 props 傳遞給子組件,從而達(dá)到頁(yè)面更新的效果。
React.js 這種重新渲染的機(jī)制幫助我們免除了絕大部分的 DOM 更新操作芬膝,也讓類(lèi)似于 jQuery 這種以封裝 DOM 操作為主的第三方的庫(kù)從我們的開(kāi)發(fā)工具鏈中刪除。
但是 React.js 并不能完全滿(mǎn)足所有 DOM 操作需求,有些時(shí)候我們還是需要和 DOM 打交道乍惊。比如說(shuō)你想進(jìn)入頁(yè)面以后自動(dòng) focus 到某個(gè)輸入框,你需要調(diào)用 input.focus() 的 DOM API解幼,比如說(shuō)你想動(dòng)態(tài)獲取某個(gè) DOM 元素的尺寸來(lái)做后續(xù)的動(dòng)畫(huà)抑党,等等。
React.js 當(dāng)中提供了 ref 屬性來(lái)幫助我們獲取已經(jīng)掛載的元素的 DOM 節(jié)點(diǎn)撵摆,你可以給某個(gè) JSX 元素加上 ref屬性:
class AutoFocusInput extends Component {
componentDidMount () {
this.input.focus()
}
render () {
return (
<input ref={(input) => this.input = input} />
)
}
}
ReactDOM.render(
<AutoFocusInput />,
document.getElementById('root')
)
可以看到我們給 input 元素加了一個(gè) ref 屬性底靠,這個(gè)屬性值是一個(gè)函數(shù)。當(dāng) input 元素在頁(yè)面上掛載完成以后特铝,React.js 就會(huì)調(diào)用這個(gè)函數(shù)暑中,并且把這個(gè)掛載以后的 DOM 節(jié)點(diǎn)傳給這個(gè)函數(shù)。在函數(shù)中我們把這個(gè) DOM 元素設(shè)置為組件實(shí)例的一個(gè)屬性鲫剿,這樣以后我們就可以通過(guò) this.input 獲取到這個(gè) DOM 元素鳄逾。
然后我們就可以在 componentDidMount 中使用這個(gè) DOM 元素,并且調(diào)用 this.input.focus() 的 DOM API灵莲。整體就達(dá)到了頁(yè)面加載完成就自動(dòng) focus 到輸入框的功能(大家可以注意到我們用上了 componentDidMount 這個(gè)組件生命周期)雕凹。
我們可以給任意代表 HTML 元素標(biāo)簽加上 ref 從而獲取到它 DOM 元素然后調(diào)用 DOM API。但是記住一個(gè)原則:能不用 ref 就不用政冻。特別是要避免用 ref 來(lái)做 React.js 本來(lái)就可以幫助你做到的頁(yè)面自動(dòng)更新的操作和事件監(jiān)聽(tīng)枚抵。多余的 DOM 操作其實(shí)是代碼里面的“噪音”,不利于我們理解和維護(hù)明场。
順帶一提的是汽摹,其實(shí)可以給組件標(biāo)簽也加上 ref ,例如:
<Clock ref={(clock) => this.clock = clock} />
這樣你獲取到的是這個(gè) Clock 組件在 React.js 內(nèi)部初始化的實(shí)例苦锨。但這并不是什么常用的做法逼泣,而且也并不建議這么做趴泌,所以這里就簡(jiǎn)單提及,有興趣的朋友可以自己學(xué)習(xí)探索圾旨。
13踱讨、props.children 和容器類(lèi)組件
有一類(lèi)組件,充當(dāng)了容器的作用砍的,它定義了一種外層結(jié)構(gòu)形式痹筛,然后你可以往里面塞任意的內(nèi)容。這種結(jié)構(gòu)在實(shí)際當(dāng)中非常常見(jiàn)廓鞠,例如這種帶卡片組件:
組件本身是一個(gè)不帶任何內(nèi)容的方形的容器帚稠,我可以在用這個(gè)組件的時(shí)候給它傳入任意內(nèi)容:
基于我們目前的知識(shí)儲(chǔ)備,可以迅速寫(xiě)出這樣的代碼:
class Card extends Component {
render () {
return (
<div className='card'>
<div className='card-content'>
{this.props.content}
</div>
</div>
)
}
}
ReactDOM.render(
<Card content={
<div>
<h2>React.js 小書(shū)</h2>
<div>開(kāi)源床佳、免費(fèi)滋早、專(zhuān)業(yè)、簡(jiǎn)單</div>
訂閱:<input />
</div>
} />,
document.getElementById('root')
)
我們通過(guò)給 Card
組件傳入一個(gè) content
屬性砌们,這個(gè)屬性可以傳入任意的 JSX 結(jié)構(gòu)杆麸。然后在 Card
內(nèi)部會(huì)通過(guò) {this.props.content}
把內(nèi)容渲染到頁(yè)面上。
這樣明顯太丑了浪感,如果 Card
除了 content
以外還能傳入其他屬性的話(huà)昔头,那么這些 JSX 和其他屬性就會(huì)混在一起。很不好維護(hù)影兽,如果能像下面的代碼那樣使用 Card
那想必也是極好的:
ReactDOM.render(
<Card>
<h2>React.js 小書(shū)</h2>
<div>開(kāi)源揭斧、免費(fèi)、專(zhuān)業(yè)峻堰、簡(jiǎn)單</div>
訂閱:<input />
</Card>,
document.getElementById('root')
)
如果組件標(biāo)簽也能像普通的 HTML 標(biāo)簽?zāi)菢泳帉?xiě)內(nèi)嵌的結(jié)構(gòu)讹开,那么就方便很多了。實(shí)際上捐名,React.js 默認(rèn)就支持這種寫(xiě)法旦万,所有嵌套在組件中的 JSX 結(jié)構(gòu)都可以在組件內(nèi)部通過(guò) props.children
獲取到:
class Card extends Component {
render () {
return (
<div className='card'>
<div className='card-content'>
{this.props.children}
</div>
</div>
)
}
}
把 props.children
打印出來(lái),你可以看到它其實(shí)是個(gè)數(shù)組:
React.js 就是把我們嵌套的 JSX 元素一個(gè)個(gè)都放到數(shù)組當(dāng)中镶蹋,然后通過(guò) props.children
傳給了 Card
纸型。
由于 JSX 會(huì)把插入表達(dá)式里面數(shù)組中的 JSX 一個(gè)個(gè)羅列下來(lái)顯示。所以其實(shí)就相當(dāng)于在 Card
中嵌套了什么 JSX 結(jié)構(gòu)梅忌,都會(huì)顯示在 Card
的類(lèi)名為 card-content
的 div
元素當(dāng)中狰腌。
這種嵌套的內(nèi)容成為了 props.children
數(shù)組的機(jī)制使得我們編寫(xiě)組件變得非常的靈活,我們甚至可以在組件內(nèi)部把數(shù)組中的 JSX 元素安置在不同的地方:
class Layout extends Component {
render () {
return (
<div className='two-cols-layout'>
<div className='sidebar'>
{this.props.children[0]}
</div>
<div className='main'>
{this.props.children[1]}
</div>
</div>
)
}
}
這是一個(gè)兩列布局組件牧氮,嵌套的 JSX 的第一個(gè)結(jié)構(gòu)會(huì)成為側(cè)邊欄琼腔,第二個(gè)結(jié)構(gòu)會(huì)成為內(nèi)容欄,其余的結(jié)構(gòu)都會(huì)被忽略踱葛。這樣通過(guò)這個(gè)布局組件丹莲,就可以在各個(gè)地方高度復(fù)用我們的布局光坝。
總結(jié)
使用自定義組件的時(shí)候,可以在其中嵌套 JSX 結(jié)構(gòu)甥材。嵌套的結(jié)構(gòu)在組件內(nèi)部都可以通過(guò) props.children
獲取到盯另,這種組件編寫(xiě)方式在編寫(xiě)容器類(lèi)型的組件當(dāng)中非常有用。而在實(shí)際的 React.js 項(xiàng)目當(dāng)中洲赵,我們幾乎每天都需要用這種方式來(lái)編寫(xiě)組件鸳惯。