React從入門到精通系列之(5)state管理和生命週期鉤子

State和生命週期

考慮前面部分中的滴答時鐘示例(第三章)。
到目前爲止,咱們只學習了一種更新UI的方法。
咱們調用ReactDOM.render()來改變渲染輸出:javascript

import React from 'react';
import ReactDOM from 'react-dom';

function tick() {
    const element = (
        <div>
            <h1>Hell world</h1>
            <h2>It is {new Date().toLocaleTimeString()}</h2>
        </div>
    );
    ReactDOM.render(
        element,
        document.getElementById('root')
    );
}

setInterval(tick, 1000);

在本節中,咱們將學習如何使Clock組件真正可重用和封裝。 它將設置本身的計時器並每秒更新一次。
咱們能夠從封裝時鐘的外觀開始:java

import React from 'react';
import ReactDOM from 'react-dom';

function Clock(props) {
    return (
        <div>
            <h1>hello world</h1>
            <h2>It is {props.date.toLocaleTimeString()}</h2>
        </div>
    );
}

function tick() {
   ReactDOM.render(
       <Clock date={new Date()} />,
       document.getElementById('root')
   );
}

setInterval(tick, 1000);

然而,它缺乏了一個關鍵要求:時鐘設置一個定時器和每秒更新UI的事實應該是時鐘的實現細節。
理想狀況下,咱們要寫這一次,並由時鐘自己來更新時間:react

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

要實現這一點,咱們須要添加「state」到時鐘組件。瀏覽器

state相似於props,但它是私有的,徹底由組件控制。dom

咱們以前提到,定義爲類組件具備一些附加功能。 內部state就是:一個只有類組件可用的功能。異步

將函數形式組件改成類形式組件

您能夠經過五個步驟將功能組件(如Clock)轉換爲類組件 :函數

  1. 建立一個與擴展React.Component相同名稱的ES6類。post

  2. 爲它添加一個單一的空方法render()性能

  3. 將函數的主體移動到render()方法中。學習

  4. render()主體中用this.props替換props

  5. 刪除剩餘的空函數聲明。

class Clock extends React.Component {
   render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
           </div>
       )
   };
}

Clock如今已經從新定義爲類組件而不是以前的功能組件了。
這使咱們可使用額外的功能,如內部state和生命週期鉤子。

向類組件中添加state

咱們將分爲三個步驟把dateprops移動到state

1)在render()方法中將this.props.date替換爲this.state.date
class Clock extends React.Component {
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
}
2)添加一個賦值初始this.state的類構造函數:
class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>It is {this.state.date.toLocalTimeString()}.</h2>
            </div>
        );
    }
}

注意咱們如何將props傳遞給基類的構造函數:

constructor(props) {
    super(props);
    this.state = {date: new Date()};
}

類組件應該老是用props調用基類構造函數。

3)從<Clock />元素中刪除date prop:
ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

咱們稍後將定時器代碼添加回組件自己。
結果以下所示:

import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

接下來,咱們將使時鐘設置本身的定時器,並每秒更新一次。

向類中添加聲明週期方法

在具備許多組件的應用程序中,釋放組件在銷燬時佔用的資源很是重要。
咱們想要在第一次將時鐘渲染到DOM時設置一個計時器。 這在React中稱爲「安裝(mounting)」
咱們還想清除定時器,當時鍾產生的DOM被刪除。 這在React中稱爲「卸載(unmounting)"
咱們能夠在組件類上聲明特殊方法,以便在組件裝入和卸載時運行一些代碼:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    componentDidMount() {
        // 組件已經安裝完畢
    }
    
    componentWillUnmount() {
        // 組件將要被卸載
    }
    
    render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}

這些方法稱爲「生命週期鉤子」
componentDidMount()子在組件輸出呈現到DOM以後運行。 這是設置計時器的好地方:

componentDidMount() {
    this.timerID = setInterval(
        () => this.tick(),
        1000
    )
}

注意咱們如何保存計時器ID就在這。
雖然this.props是由React自己設置的,而且this.state有一個特殊的含義,若是你須要存儲不用於視覺輸出的東西,你能夠手動地添加額外的字段到類中。
若是你不使用render()中的東西,它不該該放置在state中。
咱們將拆除componentWillUnmount()生命週期鉤子中的計時器:

componentWillUnmount() {
    clearInterval(this.timerID);
}

最後,咱們將實現每秒運行的tick()方法。
它將使用this.setState()來調度組件本地state的更新:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    componentDidMount() {
        this.timerID = setInterval(
            () => this.tick(),
            1000
        )
    }
    
    componentWillUnmount() {
        clearInterval(this.timerID);
    }

    tick() {
        this.setState({
            date: new Date()
        });
    }
    
    render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}
ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

如今時鐘每秒鐘都在滴答地走,棒不棒。。。。

讓咱們快速回顧一下發生了什麼以及調用方法的順序:

  • 1)當將<Clock />傳遞給ReactDOM.render()時,React調用Clock組件的構造函數。因爲Clock須要顯示當前時間,它使用包括當前時間的對象初始化this.state。咱們稍後將更新此state。

  • 2)React而後調用Clock組件的render()方法。這是React如何學習應該在屏幕上顯示什麼。 React而後更新DOM以匹配時鐘的渲染輸出。

  • 3)當時鍾輸出插入到DOM中時,React調用componentDidMount()生命週期鉤子。在其中,時鐘組件要求瀏覽器設置一個定時器,每秒調用tick()一次。

  • 4)每秒鐘瀏覽器調用tick()方法。在其中,Clock組件經過調用setState()和包含當前時間的對象來調度UI更新。因爲setState()調用,React知道state已更改,並再次調用render()方法來了解屏幕上應該顯示的內容。這個時候,render()方法中的this.state.date將會不一樣,所以渲染輸出將包括更新的時間。 React相應地更新DOM。

  • 5)若是時鐘組件從DOM中被移除,React將調用componentWillUnmount()生命週期鉤子,所以定時器中止。

正確使用state

關於setState()你應該瞭解三件事情:

不要直接修改state

例如,這將不會從新渲染組件:

// 這是錯誤的
this.state.comment = 'hello';

應該使用setState()代替:

// 這是正確的
this.setState({comment: 'hello'});

惟一能夠分配this.state的地方是構造函數。

state更新多是異步的

React能夠將多個setState()用批處理爲單個更新以實現較高的性能。
由於this.propsthis.state多是異步更新的,你不該該依賴它們的值來計算下一個state。
例如,此代碼可能沒法更新計數器:

// 這是錯誤的
this.setState({
    counter: this.state.counter + this.props.increment,
});

要解決它,應該使用回調函數而不是對象來調用setState()。 回調函數將接收先前的state做爲第一個參數,並將應用更新時的props做爲第二個參數:

// 這是正確的
this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
}));

咱們使用上面的箭頭函數,但它也能夠與常規函數一塊兒使用:

// 這一樣也是正確的,將剪頭函數改成普通函數
this.setState(function(prevState, props) {
   return {
       counter: prevState.counter + prps.increment
   }
});
state更新是通過合併的

當調用setState()時,React會將您提供的對象合併到當前state。
例如,您的state可能包含幾個獨立變量:

constructor(props) {
    super(props);
    this.state = {
        posts: [],
        comments: []
    }
}

而後,您可使用單獨的setState()來獨立地更新它們:

componentDidMount() {
    fetchPosts().then(response => {
        this.setState({
            posts: response.posts
        });
    });
    
    fetchComments().then(response => {
        this.setState({
            comments: response.comments
        }});
    });
}

合併很淺,因此this.setState({comments})不會波及this.state.posts。僅僅只是徹底替換了this.state.comments而已。

數據是向下流動的

父組件和子組件都不能知道某個組件是有State的仍是無State的,而且它們不該該關心它是否爲功能組件或類組件。

這就是爲何State一般被設置爲局部變量或封裝到組件內部。 除了擁有和設置它的組件以外的其餘任何組件都不能訪問它。

組件能夠選擇將其state做爲props傳遞給其子組件:

<h2>Is is {this.state.date.toLocaleTimeString()}.</h2>

這也適用於用戶定義的組件:

<formattedDate date={this.state.data} />

FormattedDate組件將在其props中接收date,而且不知道它是來自時鐘的stateprops仍是手動輸入

function FormattedData(props) {
    return <h2>Is is {props.date.toLocaleTimeString()}.</h2>;
}

這一般被稱爲「自頂向下」「單向」數據流。 任何state老是由一些特定組件擁有,而且從該state派生的任何數據或UI只能影響樹中的「下面」組件。

若是你想象一個組件樹做爲props的瀑布流,每一個組件的state就像一個額外的水源,它能夠在任意點鏈接它,但也向下流。

爲了顯示全部組件都是真正隔離的,咱們能夠建立一個App組件來渲染三個<Clock>

function App() {
    return (
        <div>
            <Clock />
            <Clock />
            <Clock />
        </div>
    );
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
);

每一個時鐘設置本身的定時器並獨立更新。在React應用程序中,組件是有狀態仍是無狀態被視爲可能隨時間更改的組件的實現細節。 您能夠在有狀態組件內使用無狀態組件,反之亦然。

相關文章
相關標籤/搜索