react總結(jié)(摘自react小書(shū))

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ū)"
        )
      )
    )
  }
44B5EC06-EAEB-4BA2-B3DC-325703E4BA45.png

有些同學(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)系:

19BBE4E2-A12E-4657-BA6A-61484F67FA60.png

當(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)什么:

3ADE3817-7D91-4462-830D-1802D8345326.png

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è)面上顯示:

05FD6746-FEF5-4253-9802-EB563643DEDC.png

但我們一般不會(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ò) propsuser 數(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)看看:

85CA5037-99C1-422C-99A4-AADA978C6801.png

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>

cb 的位置互換了。但其實(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()

componentWillMountcomponentDidMount 都是可以像 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)你可以看到依次輸出:

69676213-FDED-4E60-8142-07599BA10696.png

可以看到或粮,React.js 確實(shí)按照我們上面所說(shuō)的那樣調(diào)用了定義的兩個(gè)方法 componentWillMountcomponentDidMount

機(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.isShowHeadertrue 的時(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)用了:

B396B6CF-50F1-4C4E-9D16-4E746F15F91F.png

你可以多次點(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ō)剩胁,constructorcomponentWillMount祥国、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)用:

FECF7A01-5C87-4E03-AA98-03BB30538C66.png

我們會(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ò)了:

340BBCEA-35CC-4B35-B352-267F381477EF.png

這是因?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)于更新階段的組件生命周期:

  1. shouldComponentUpdate(nextProps, nextState):你可以通過(guò)這個(gè)方法控制組件是否重新渲染香嗓。如果返回 false 組件就不會(huì)重新渲染。這個(gè)生命周期在 React.js 性能優(yōu)化上非常有用装畅。
  2. componentWillReceiveProps(nextProps):組件從父組件接收到新的 props 之前調(diào)用靠娱。
  3. componentWillUpdate():組件開(kāi)始重新渲染之前調(diào)用。
  4. 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)廓鞠,例如這種帶卡片組件:

![6BD73C14-60FE-44BA-A93C-B637BD07DE59.png](https://upload-images.jianshu.io/upload_images/8805811-47500f40f8b9ee21.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

組件本身是一個(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ù)組:

4CD84934-5A7F-4942-A5F5-3C935E113499.png

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-contentdiv 元素當(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ě)組件鸳惯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市叠萍,隨后出現(xiàn)的幾起案子芝发,更是在濱河造成了極大的恐慌,老刑警劉巖苛谷,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辅鲸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡腹殿,警方通過(guò)查閱死者的電腦和手機(jī)独悴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锣尉,“玉大人绵患,你說(shuō)我怎么就攤上這事∥蛟牛” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵织狐,是天一觀(guān)的道長(zhǎng)暂幼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)移迫,這世上最難降的妖魔是什么旺嬉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮厨埋,結(jié)果婚禮上邪媳,老公的妹妹穿的比我還像新娘。我一直安慰自己荡陷,他們只是感情好雨效,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著废赞,像睡著了一般徽龟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唉地,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天据悔,我揣著相機(jī)與錄音传透,去河邊找鬼。 笑死极颓,一個(gè)胖子當(dāng)著我的面吹牛朱盐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菠隆,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼兵琳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了浸赫?” 一聲冷哼從身側(cè)響起闰围,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎既峡,沒(méi)想到半個(gè)月后羡榴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡运敢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年校仑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片传惠。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迄沫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卦方,到底是詐尸還是另有隱情羊瘩,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布盼砍,位于F島的核電站尘吗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏浇坐。R本人自食惡果不足惜睬捶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望近刘。 院中可真熱鬧擒贸,春花似錦、人聲如沸觉渴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)案淋。三九已至蜕猫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哎迄,已是汗流浹背回右。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工隆圆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翔烁。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓渺氧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蹬屹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子侣背,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 說(shuō)在前面 關(guān)于 react 的總結(jié)過(guò)去半年就一直碎碎念著要搞起來(lái),各(wo)種(tai)原(lan)因(le)慨默。心...
    陳嘻嘻啊閱讀 6,846評(píng)論 7 41
  • $ 前言 ? 最近在考慮框架轉(zhuǎn)型贩耐,鑒于作為一名JSer,要時(shí)時(shí)刻刻保持對(duì)新技術(shù)和流行技術(shù)的敏感性厦取,而 React潮太、...
    果汁涼茶丶閱讀 21,987評(píng)論 5 32
  • 原教程內(nèi)容詳見(jiàn)精益 React 學(xué)習(xí)指南,這只是我在學(xué)習(xí)過(guò)程中的一些閱讀筆記虾攻,個(gè)人覺(jué)得該教程講解深入淺出铡买,比目前大...
    leonaxiong閱讀 2,810評(píng)論 1 18
  • 最近看了一本關(guān)于學(xué)習(xí)方法論的書(shū),強(qiáng)調(diào)了記筆記和堅(jiān)持的重要性霎箍。這幾天也剛好在學(xué)習(xí)React奇钞,所以我打算每天堅(jiān)持一篇R...
    gaoer1938閱讀 1,666評(píng)論 0 5
  • HTML模版 之后出現(xiàn)的React代碼嵌套入模版中。 1. Hello world 這段代碼將一個(gè)一級(jí)標(biāo)題插入到指...
    ryanho84閱讀 6,221評(píng)論 0 9