react 的生命週期鉤子函式
上篇文章我們學習了 state,用以跟蹤元件的內部狀態。 今天,我們將暫停具體的元件實現,轉而來談談 react 元件在應用程式中是如何存在的,也就是 react 元件的生命週期。
當應用程式使用 React 框架時,就可以使用該框架提供的一些生命週期鉤子函式,以便我們在元件的各個生命週期中注入自定義的功能。 為了掛鉤生命週期,我們需要在 React 元件中定義特定的函式,以便 React 在適當的時候呼叫。 接下來,讓我們深入瞭解第一個生命週期鉤子:
componentWillMount() / componentDidMount()
誠如,我們在之前的文章介紹的,React 元件 render 的是虛擬 DOM,我們只有等元件在瀏覽器中掛載之後,才能使用其對應的真實 DOM。
- componentWillMount : 元件將要在頁面上掛載之前呼叫
- componentDidMount : 元件在頁面掛載之後立即呼叫
什麼是掛載 ?
React 渲染的實際是DOM樹的虛擬節點,並沒有定義真實的DOM節點。 因此,React 在記憶體之中為我們維護和管理的一套虛擬檢視。 我們談到的掛載,實際上就是將虛擬元件轉換為與之對應的真實DOM元素的過程。
我們通常在這兩個生命週期中通過 GET 請求來獲取資料實現動態元件。
還記得我們之前的 ListPage 嗎,我們今天還要用到它!
class ListContent extends React.Component {
render() {
return (
<div className="list-content">
<ul>
{
this.props.list.map(list => <ListItem listData = { list } />)
}
</ul>
</div>
)
}
}
class ListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
listData: [{
avatar: 'http://www.croop.cl/UI/twitter/images/doug.jpg',
time: 10,
content: '學習React好開心呀!'
}, {
avatar: 'http://www.croop.cl/UI/twitter/images/doug.jpg',
time: 5,
content: 'React應用程式的核心都是元件。 元件是一個自包含的模組,可以渲染一些介面元素。 我們可以將按鈕或輸入欄位等介面元素編寫為React元件。 '
}, {
avatar: 'http://www.croop.cl/UI/twitter/images/doug.jpg',
time: 8,
content: 'React不是直接在瀏覽器的文件物件模型(DOM)上執行,而是在虛擬 DOM 上執行,很大程度上提高了渲染的速度。'
}, {
avatar: 'http://www.croop.cl/UI/twitter/images/doug.jpg',
time: 1,
content: 'React 虛擬 DOM 完全存在於記憶體中,與 Web瀏覽器中的真是 DOM 有著一定地對應關係。 因此,當我們編寫React元件時,我們不是直接寫入DOM,而是編寫 DOM 的React虛擬元件。 '
}, {
avatar: 'http://www.croop.cl/UI/twitter/images/doug.jpg',
time: 13,
content: '元件是可組合的,一個複雜的元件可以包含多個基本元件。'
}]
}
}
render() {
return (
<div className="list-page">
<Header title="React-30-Days"/>
<ListContent list={this.state.listData} />
</div>
)
}
}
這一段程式碼,我們是通過寫死 this.state.listData 來初始化 list,但是在實際應用中,我們一般會通過 http 請求來動態獲取 list 資料, 這時我們就需要這兩個鉤子函式。
class ListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
listData: [], // 初始化 state
};
}
componentDidMount() {
const self = this;
$.ajax({ // 使用 jquery 的 ajax 請求
type: "GET",
url: 'https://www.iamyangqi.cn/react-30-days/list.json',
success: (json) => {
const list = json;
self.setState({
listData: list.listData,
})
}
})
}
render() {
return (
<div className="list-page">
<Header title="React-30-Days"/>
<ListContent list={this.state.listData} />
</div>
)
}
}
同樣,可以動態渲染出原先的列表:
componentWillMount
componentWillMount 是在元件第一次執行render前執行 ,乍一看它時處理獲取資料邏輯的很完美的地方。但是我們一般都是通過非同步請求API的方式來獲取資料,這就意味著我們還沒有獲取到資料而render方法已經被執行了。我們沒有辦法暫停render的執行來等待資料的到來。
componentDidMount
componentWillMount 是在render被執行之後執行的,在實踐上是獲取資料最好的位置。原因如下:
- componentDidMount是在元件初始化後才被執行的,這就需要我們正確的初始化state.否則將會出錯
- 如果你需要在伺服器端渲染應用程式,componentWillMount將被呼叫兩次。一次是在server端,一次在客戶端,但這並不是你想要的結果而將資料載入邏輯放在componentDidMount將確保資料只從客戶端獲取
componentWillUpdate() / componentDidUpdate()
有時我們會想要在實際渲染之前或之後更新元件的某些資料時,這兩個鉤子就是合適的地方。
- componentWillUpdate: 元件初始化時不呼叫,只有在元件將要更新時才呼叫
- componentDidUpdate: 元件初始化時不呼叫,元件更新完成後呼叫,此時可以獲取dom節點
相較於這兩個生命週期鉤子,我們實際上更常用另一個響應元件更新的鉤子 — componentWillReceiveProps 。
componentWillReceiveProps(nextProps)
這是元件接收新 props 後第一個呼叫的鉤子方法,這是計算更改並更新元件內部狀態的最佳地方。
注意: 即使 componentWillReceiveProps 方法被呼叫,也存在 props 沒有發生改變的可能,因此檢測 props 的變化是很有必要的,以免造成不必要的重新渲染。
再次重寫上面的栗子,在左上角的排序按鈕添上點選事件,為了看到componentWillReceiveProps的作用,我們會在listContent元件的componentWillReceiveProps 鉤子中獲取排序的程式碼,並根據這個程式碼設定排序文字。
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
sort: 0, // 0 -- 亂序 , 1 -- 升序, 2 -- 降序
}
}
sort = () => {
let { sort } = this.state;
sort = (sort + 1) % 3;
this.setState({
sort
})
this.props.getSort(sort); // props 可以傳遞一個函式
}
render() {
const {title} = this.props;
return (
<header className="list-header">
<i className="iconfont icon-sousuo"></i>
<span>{this.props.title}</span>
<i className="iconfont icon-paixu" onClick={ this.sort }></i>
</header>
)
}
}
class ListContent extends React.Component {
sortText = '亂序';
constructor(props) {
super(props);
this.state = {
sort: 0,
}
}
componentWillReceiveProps(nextProps) {
if (this.props !== nextProps) {
let text = ''
switch (nextProps.sort) {
case 0: text = '亂序'; break;
case 1: text = '升序'; break;
case 2: text = '降序'; break;
default: text = ''; break;
}
this.sortText = text;
}
}
render() {
return (
<div className="list-content">
<ul>
{
this.props.list.map(list => <ListItem listData = { list } />)
}
</ul>
<p>排序方式: {this.sortText}</p>
</div>
)
}
}
class ListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
listData: [], // 初始化 state
sortList: [],
sort: 0,
};
}
getSort = (sort) => {
const state = { ...this.state};
if(sort === 0) {
state.sortList = state.listData;
} else if(sort === 1) {
state.sortList = state.sortList.sort((a, b) => { return a.time - b.time})
} else if(sort === 2) {
state.sortList = state.sortList.sort((a, b) => { return b.time - a.time})
}
state.sort = sort;
this.setState(state);
}
componentDidMount() {
const self = this;
$.ajax({
type: "GET",
url: 'https://www.iamyangqi.cn/react-30-days/list.json',
success: (json) => {
const list = json;
self.setState({
listData: list.listData,
sortList: list.listData.slice(),
})
}
})
}
render() {
return (
<div className="list-page">
<Header title="React-30-Days" getSort = {this.getSort}/>
<ListContent list={this.state.sortList} sort={this.state.sort}/>
</div>
)
}
}
componentWillUnmount()
有掛載,當然就有解除安裝,不然就有記憶體洩露的危險。componentWillUnmount 就是 react 解除安裝元件之前呼叫的鉤子。在這裡,我們可以清理那些需要被清理的事件,比如: 處理timeout、清除資料、斷開 websocket 連線等。
例如,之前使用的時鐘元件,我們設定每秒呼叫一次 setTimeout,但當元件準備解除安裝時,我們希望確保清除此 timer,以便 JavaScript 不會再繼續為這些實際不存在的元件執行 timer 。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = this.getTime;
this.setTimer();
}
get getTime () {
const currentTime = new Date(),
hours = currentTime.getHours(),
minutes = currentTime.getMinutes(),
seconds = currentTime.getSeconds(),
ampm = hours >= 12 ? 'pm' : 'am';
return {hours, minutes, seconds, ampm}
}
setTimer() {
clearTimeout(this.timeout);
this.timeout = setTimeout(this.updateClock.bind(this), 1000); // 使用 setTimeout 要比使用 setInterval 計時更為準確
}
updateClock() {
this.setState(this.getTime, this.setTimer);
this.setTimer();
}
componentWillUnmount() { // 在這裡取消定時器
if (this.timeout) {
clearTimeout(this.timeout);
}
}
render() {
const {hours, minutes, seconds, ampm} = this.state;
return (
<div className="clock">
{
hours == 0 ? 12 :
(hours > 12) ?
hours - 12 : hours
}:{
("00" + minutes).slice(-2)
}:{
("00" + seconds).slice(-2)
} {ampm}
</div>
)
}
}
好了,以上就是我們介紹的一些常用的生命週期的鉤子,想要了解更多,可以問度娘。下一篇,我們將介紹 props 屬性的型別 — propTypes。