一、組件的生命周期
1.概念:
在組件創(chuàng)建、到加載到頁面上運(yùn)行、以及組件被銷毀的過程中,總是伴隨著各種各樣的事件,這些在組件特定時(shí)期,觸發(fā)的事件,統(tǒng)稱為 組件的生命周期;
2.組件生命周期分為三部分:
- 組件創(chuàng)建階段:組件創(chuàng)建階段的生命周期函數(shù),有一個(gè)顯著的特點(diǎn):創(chuàng)建階段的生命周期函數(shù),在組件的一輩子中,只執(zhí)行一次;
componentWillMount: 組件將要被掛載,此時(shí)還沒有開始渲染虛擬DOM,此時(shí)props和state中的數(shù)據(jù)都能獲取到。
render:第一次開始渲染真正的虛擬DOM,當(dāng)render執(zhí)行完,內(nèi)存中就有了完整的虛擬DOM了
componentDidMount: 組件完成了掛載(掛載即把虛擬DOM樹顯示到頁面上),此時(shí),組件已經(jīng)顯示到了頁面上,當(dāng)這個(gè)方法執(zhí)行完,組件就進(jìn)入了 運(yùn)行中 的狀態(tài)
- 組件運(yùn)行階段:也有一個(gè)顯著的特點(diǎn),根據(jù)組件的state和props的改變,有選擇性的觸發(fā)0次或多次;
componentWillReceiveProps: 組件將要接收新屬性,此時(shí),只要這個(gè)方法被觸發(fā),就證明父組件為當(dāng)前子組件傳遞了新的屬性值;
shouldComponentUpdate: 組件是否需要被更新,此時(shí),組件尚未被更新,但是,state 和 props 肯定是最新的
componentWillUpdate: 組件將要被更新,此時(shí),尚未開始更新,內(nèi)存中的虛擬DOM樹還是舊的
render: 此時(shí),又要重新根據(jù)最新的 state 和 props 重新渲染一棵內(nèi)存中的虛擬DOM樹,當(dāng) render 調(diào)用完畢,內(nèi)存中的舊DOM樹,已經(jīng)被新DOM樹替換了!此時(shí)頁面還是舊的
componentDidUpdate: 此時(shí),頁面又被重新渲染了,state 和 虛擬DOM 和 頁面已經(jīng)完全保持同步
- 組件銷毀階段:也有一個(gè)顯著的特點(diǎn),一輩子只執(zhí)行一次;
componentWillUnmount: 組件將要被卸載,此時(shí)組件還可以正常使用;
vue中的生命周期圖
React Native 中組件的生命周期

3.defaultProps
在組件創(chuàng)建之前,會先初始化默認(rèn)的props屬性,這是全局調(diào)用一次,嚴(yán)格地來說,這不是組件的生命周期的一部分。在組件被創(chuàng)建并加載候,首先調(diào)用 constructor 構(gòu)造器中的 this.state = {},來初始化組件的狀態(tài)。
React生命周期的回調(diào)函數(shù)總結(jié)成表格如下:

4.組件生命周期的執(zhí)行順序:
- Mounting:
- constructor()
- componentWillMount()
- render()
- componentDidMount()
- Updating:
- componentWillReceiveProps(nextProps)
- shouldComponentUpdate(nextProps, nextState)
- componentWillUpdate(nextProps, nextState)
- render()
- componentDidUpdate(prevProps, prevState)
- Unmounting:
- componentWillUnmount()
二、通過Counter計(jì)數(shù)器的小案例 - 了解生命周期函數(shù)
- 給組件設(shè)置默認(rèn)屬性:
// 在封裝一個(gè)組件的時(shí)候,組件內(nèi)部,肯定有一些數(shù)據(jù)是必須的,哪怕用戶沒有傳遞一些相關(guān)的啟動(dòng)參數(shù),這時(shí)候,組件內(nèi)部 ,盡量 給自己提供一個(gè)默認(rèn)值;
// 在 React 中,使用靜態(tài)的 defaultProps 屬性,來設(shè)置 組件的 默認(rèn)屬性值;
static defaultProps = {
initcount: 0 // 如果外界沒有傳遞 initcount,那么,自己初始化一個(gè) 數(shù)值,為0
}
- 給屬性進(jìn)行類型校驗(yàn),需要先運(yùn)行
cnpm i prop-types --save
// 注意: prop-types 包中職能跟單一,只提供了 一些常見的 數(shù)據(jù)類型,用于做類型校驗(yàn)
import ReactTypes from 'prop-types'
// 這是創(chuàng)建一個(gè) 靜態(tài)的 propTypes 對象,在這個(gè)對象中,可以把 外界傳遞過來的屬性,做類型校驗(yàn);
// 注意: 如果要為 傳遞過來的屬性做類型校驗(yàn),必須安裝 React 提供的 第三方包,叫做 prop-types ;
// prop-types 大概在 v.15.* 之前,并沒有單獨(dú)抽離出來,那時(shí)候,還和 react 包 在一起;后來, 從 v.15.* 之后,官方把類型校驗(yàn)的 模塊,單獨(dú)抽離為 一個(gè)包,就叫做 prop-types
static propTypes = {
initcount: ReactTypes.number // 使用 prop-types 包,來定義 initcount 為 number 類型
}
組件初始化時(shí)生命周期事件總結(jié)
componentWillMount:等同于Vue中的created生命周期函數(shù)。 此時(shí),props和state中的數(shù)據(jù)都能獲取到。但無法獲取到頁面上的任何元素,因?yàn)樘摂MDOM和頁面都還沒有開始渲染。【在這個(gè)階段中,不能去操作頁面上的DOM元素】
render:即將要開始渲染內(nèi)存中的虛擬DOM,當(dāng) return 執(zhí)行完畢后,虛擬DOM創(chuàng)建好了,但是只是存在于內(nèi)存中,還沒有掛載到真正的頁面中。
componentDidMount:相當(dāng)于 Vue 中的 mounted 函數(shù)。當(dāng)組件掛載到頁面上之后,就是進(jìn)入這個(gè)生命周期函數(shù)了,頁面上已經(jīng)有可見的DOM元素了。組件就就進(jìn)入到了運(yùn)行中的狀態(tài)。如果我們想操作DOM元素,最早只能在 componentDidMount 中進(jìn)行;
通過原生的方式獲取元素并綁定事件
componentDidMount() {
document.getElementById('btn').onclick = () => {
// console.log(this);
// this.props.initcount++
this.setState({
count: this.state.count + 1
})
}
}
React中使用ref屬性獲取DOM元素引用
和 Vue 中差不多,vue 為頁面上的元素提供了 ref 的屬性,如果想要獲取元素引用,則需要使用this.$refs.引用名稱
在 React 中,也有 ref, 如果要獲取元素的引用this.refs.引用名稱
<h3 id="myh3" ref="h3">當(dāng)前的數(shù)量是:{this.state.count}</h3>
componentWillUpdate() {
console.log(document.getElementById('myh3').innerHTML)
console.log(this.refs.h3.innerHTML);
}
使用React中的事件,綁定count自增
<input type="button" value="+1" id="btn" onClick={this.increment} />
increment = () => {
this.setState({
count: this.state.count + 1
})
}
組件運(yùn)行中事件的總結(jié)對比
componentWillReceiveProps:當(dāng)組件第一次被渲染到頁面上的時(shí)候,不會觸發(fā)這個(gè)函數(shù);只有當(dāng)父級通過某些事件,重新修改了傳遞給該組件的 props 數(shù)據(jù)之后,才會觸發(fā)這個(gè)函數(shù)。這時(shí)如果使用 this.props 來獲取的屬性值不是最新的,是上一次的舊屬性值;最新的屬性值需要通過參數(shù)列表來獲取。nextProps參數(shù)表示組件將要接收外界傳遞過來的新的 props 屬性值。
shouldComponentUpdate:必須返回一個(gè)布爾值,如果返回的值是 false,則不會繼續(xù)執(zhí)行后續(xù)的生命周期函數(shù),而是直接退回到了運(yùn)行中的狀態(tài),此時(shí)由于后續(xù)的 render 函數(shù)并沒有被調(diào)用,因此頁面不會被更新,但是, 組件的 state 狀態(tài)卻被修改了;
componentWillUpdate:組件將要更新但尚未更新,內(nèi)存中的虛擬DOM是舊的,頁面上的DOM 元素也是舊的。應(yīng)該慎重操作,因?yàn)槟憧赡懿僮鞯氖桥fDOM。
render:在組件運(yùn)行階段中,每當(dāng)調(diào)用 render 函數(shù)的時(shí)候,頁面上的 DOM元素,還是之前舊的。在render函數(shù)中,不能調(diào)用
setState()方法,因?yàn)闀萑胨姥h(huán)(數(shù)據(jù)被修改又要重新更新組件,又會進(jìn)入render函數(shù))。componentDidUpdate:組件完成了更新,此時(shí)state 中的數(shù)據(jù)、虛擬DOM、頁面上的DOM,都是最新的,此時(shí),你可以放心大膽的去操作頁面了
三、綁定this并傳參的三種方式
- 在事件中綁定this并傳參:每次調(diào)用都要bind綁定this
//React中駝峰方式綁定事件的js中this指向組件實(shí)例
<input type="button" value="在事件中綁定this并傳參" onClick={this.handleMsg1.bind(this, '??', '??')} />
// 在事件中綁定this并傳參
handleMsg1(arg1, arg2) {
console.log(this);
// 此時(shí)this是個(gè)null
this.setState({
msg: '在事件中綁定this并傳參:' + arg1 + arg2
});
}
- 在構(gòu)造函數(shù)(constructor中)中綁定this并傳參:調(diào)用的時(shí)候不用再手動(dòng)綁定
// 修改構(gòu)造函數(shù)中的代碼:
//當(dāng)為一個(gè)函數(shù)調(diào)用 bind 改變了this指向后,bind 函數(shù)調(diào)用后有一個(gè)返回值,就是被改變this指向后的函數(shù)的引用;bind 不會修改 原函數(shù)的 this 指向,因此需要把返回值重新賦值給原函數(shù)
this.handleMsg2 = this.handleMsg2.bind(this, '??', '??');
<input type="button" value="在構(gòu)造函數(shù)中綁定this并傳參" onClick={this.handleMsg2} />
// 在構(gòu)造函數(shù)中綁定this并傳參
handleMsg2(arg1, arg2) {
this.setState({
msg: '在構(gòu)造函數(shù)中綁定this并傳參:' + arg1 + arg2
});
}
- 用箭頭函數(shù)綁定this并傳參:
<input type="button" value="用箭頭函數(shù)綁定this并傳參" onClick={() => { this.handleMsg3('??', '??') }} />
// 用箭頭函數(shù)綁定this并傳參
handleMsg3(arg1, arg2) {
this.setState({
msg: '用箭頭函數(shù)綁定this并傳參:' + arg1 + arg2
});
}
注意,在React中DOM綁定事件不能賦值為一個(gè)函數(shù)的調(diào)用,如
onClick={this.handleMsg('arg')},因?yàn)镽eact執(zhí)行到此會直接執(zhí)行這個(gè)調(diào)用,只能賦值為一個(gè)函數(shù)
四、綁定文本框與state中的值
- 在Vue.js中,默認(rèn)可以通過
v-model指令,將表單控件和我們的data上面的屬性進(jìn)行雙向數(shù)據(jù)綁定,數(shù)據(jù)變化和頁面之間的變化是同步的! - 在React.js中,默認(rèn)沒有提供雙向數(shù)據(jù)綁定這一功能,默認(rèn)只能把
state之上的數(shù)據(jù)同步到界面的控件上,但是不能默認(rèn)實(shí)現(xiàn)把界面上數(shù)據(jù)的改變,同步到state之上,需要程序員手動(dòng)調(diào)用相關(guān)的事件來進(jìn)行逆向的數(shù)據(jù)傳輸! - 綁定文本框和state的值:
{/*只要將value屬性,和state上的狀態(tài)進(jìn)行綁定,那么這個(gè)表單元素就變成了受控表單元素,這時(shí)如果沒有調(diào)用相關(guān)的事件,是無法手動(dòng)修改表單元素中的值的*/}
<input style={{ width: '100%' }} ref="txt" type="text" value={this.state.msg} onChange={this.handleTextChange} />
// 這是文本框內(nèi)容改變時(shí)候的處理函數(shù)
handleTextChange = (e) => {
this.setState({
//msg: e.target.value
msg: this.refs.txt.value
});
}
- 注意
setState的一個(gè)問題:
// setState在保存的時(shí)候是異步地進(jìn)行保存的,所以如果想要獲取最新的,剛剛保存的那個(gè)狀態(tài),需要通過回掉函數(shù)的形式去獲取最新state
this.setState({
msg: this.refs.txt.value
// msg: e.target.value
}, function () {
// 獲取最新的state狀態(tài)值
console.log(this.state.msg);
});
五、發(fā)表評論案例
CMTList組件
import React from 'react'
import CMTItem from './CmtItem.jsx'
import CMTBox from './CmtBox.jsx'
// 評論列表組件
export default class CMTList extends React.Component {
constructor(props) {
super(props)
this.state = {
list: [
{ user: 'zs', content: '123' },
{ user: 'ls', content: 'qqq' },
{ user: 'xiaohong', content: 'www' }
]
}
}
// 在組件尚未渲染的時(shí)候,就立即 獲取數(shù)據(jù)
componentWillMount() {
this.loadCmts()
}
render() {
return <div>
<h1>這是評論列表組件</h1>
{/* 發(fā)表評論的組件 */}
{/* 相對于 Vue 中,把 父組件傳遞給子組件的 普通屬性 和 方法屬性,區(qū)別對待, 普通屬性用 props 接收, 方法 使用 this.$emit('方法名') */}
{/* react 中,只要是傳遞給 子組件的數(shù)據(jù),不管是 普通的類型,還是方法,都可以使用 this.props 來調(diào)用 */}
<CMTBox reload={this.loadCmts}></CMTBox>
<hr />
{/* 循環(huán)渲染一些評論內(nèi)容組件 */}
{this.state.list.map((item, i) => {
return <CMTItem key={i} {...item}></CMTItem>
})}
</div>
}
// 從本地存儲中加載 評論列表
loadCmts = () => {
var list = JSON.parse(localStorage.getItem('cmts') || '[]')
this.setState({
list
})
}
}
CMTItem組件
import React from 'react'
// 評論列表項(xiàng)組件
export default class CMTItem extends React.Component {
render() {
return <div style={{ border: '1px solid #ccc', margin: '10px 0' }}>
<h3>評論人:{this.props.user}</h3>
<h5>評論內(nèi)容:{this.props.content}</h5>
</div>
}
}
CMTBox組件
import React from 'react'
// 評論列表框組件
export default class CMTBox extends React.Component {
render() {
return <div>
<label>評論人:</label><br />
<input type="text" ref="user" /><br />
<label>評論內(nèi)容:</label><br />
<textarea cols="30" rows="4" ref="content"></textarea><br />
<input type="button" value="發(fā)表評論" onClick={this.postComment} />
</div>
}
postComment = () => {
// 1. 獲取到評論人和評論內(nèi)容
// 2. 從 本地存儲中,先獲取之前的評論數(shù)組
// 3. 把 最新的這條評論,unshift 進(jìn)去
// 4. 在把最新的評論數(shù)組,保存到 本地存儲中
var cmtInfo = { user: this.refs.user.value, content: this.refs.content.value }
var list = JSON.parse(localStorage.getItem('cmts') || '[]')
list.unshift(cmtInfo)
localStorage.setItem('cmts', JSON.stringify(list))
this.refs.user.value = this.refs.content.value = ''
this.props.reload()
}
}
六、擴(kuò)展
context特性
父組件
// 1. 在 父組件中,定義一個(gè)固定名稱的function,叫做 getChildContext ,內(nèi)部必須返回一個(gè)對象,就是要共享給 所有子孫自建的 數(shù)據(jù)
getChildContext() {
return {
color: this.state.color
}
}
// 2. 使用屬性校驗(yàn),規(guī)定一下傳遞給子組件的數(shù)據(jù)類型, 需要定義一個(gè)靜態(tài)的(static) childContextTypes(固定名稱,不要改)
static childContextTypes = {
color: ReactTypes.string // 規(guī)定了 傳遞給子組件的 數(shù)據(jù)類型
}
子孫組件:先進(jìn)行屬性校驗(yàn),再通過 this.context.屬性名直接使用
// 3. 上來之后,先來個(gè)屬性校驗(yàn),去校驗(yàn)一下父組件傳遞過來的參數(shù)類型
static contextTypes = {
color: ReactTypes.string // 這里,如果子組件想要使用父組件通過 context 共享的數(shù)據(jù),那么在使用之前,一定要先做一下數(shù)據(jù)類型校驗(yàn)
}
render() {
return <div>
<h5 style={{ color: this.context.color }}>這是 孫子組件 --- {this.context.color} </h5>
</div>
}
記住一串單詞組合getChildContextTypes
父組件:前3個(gè)單詞(方法)、后3個(gè)單詞(靜態(tài)屬性)
子孫組件:后兩個(gè)單詞(靜態(tài)屬性)