1. 程式人生 > >react 的生命週期鉤子函式

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。