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

作者:日期:2017-02-19 13:15:14 點擊:146

 

State和生命周期

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

function tick() {
    const element = (
                    Hell world            It is {new Date().toLocaleTimeString()}            );
    ReactDOM.render(
        element,
        document.getElementById('root')
    );
}
setInterval(tick, 1000);

在本節中,我們将學習如何使Clock組件真正可重用和封裝。 它将設置自己的計時器并每秒更新一次。
我們可以從封裝時鐘的外觀開始:

function Clock(props) {
    return (
                    hello world            It is {props.date.toLocaleTimeString()}            );
}
function tick() {
   ReactDOM.render(
       ,
       document.getElementById('root')
   );
}
setInterval(tick, 1000);

然而,它缺少了一個關鍵要求:時鐘設置一個定時器和每秒更新UI的事實應該是時鐘的實現細節。理想情況下,我們要寫這一次,并由時鐘本身來更新時間:

ReactDOM.render(
    ,
    document.getElementById('root')
);

要實現這一點,我們需要添加“state”到時鐘組件。

state類似于props,但它是私有的,完全由組件控制。

我們之前提到,定義為類組件具有一些附加功能。 内部state就是:一個隻有類組件可用的功能。

将函數形式組件改為類形式組件

您可以通過五個步驟将功能組件(如Clock)轉換為類組件 :

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

  2. 為它添加一個單一的空方法render()

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

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

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

class Clock extends React.Component {
   render() {
       return (
                          hello world               It is {this.props.date.toLocaleTimeString()}.                  )
   };
}

Clock現在已經重新定義為類組件而不是之前的功能組件了。
這使我們可以使用額外的功能,如内部state和生命周期鈎子。

向類組件中添加state

我們将分為三個步驟把dateprops移動到state

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

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

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

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

3)從元素中删除date prop:
ReactDOM.render(
    ,
    document.getElementById('root')
);

我們稍後将定時器代碼添加回組件本身。結果如下所示:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    render() {
       return (
                          hello world               It is {this.state.date.toLocaleTimeString()}.                  );
    }
}
ReactDOM.render(
    ,
    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 (
                          hello world               It is {this.state.date.toLocaleTimeString()}.                  );
    }
}

這些方法稱為“生命周期鈎子”
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 (
                          hello world               It is {this.state.date.toLocaleTimeString()}.                  );
    }
}
ReactDOM.render(
    ,
    document.getElementById('root')
);

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

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

  • 1)當将傳遞給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傳遞給其子組件:

Is is {this.state.date.toLocaleTimeString()}.

這也适用于用戶定義的組件:


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

function FormattedData(props) {
    return Is is {props.date.toLocaleTimeString()}.;
}

這通常被稱為“自頂向下”“單向”數據流。 任何state總是由一些特定組件擁有,并且從該state派生的任何數據或UI隻能影響樹中的“下面”組件。

如果你想象一個組件樹作為props的瀑布流,每個組件的state就像一個額外的水源,它可以在任意點連接它,但也向下流。

為了顯示所有組件都是真正隔離的,我們可以創建一個App組件來渲染三個

function App() {
    return (
                                                        );
}
ReactDOM.render(
    ,
    document.getElementById('root')
);

每個時鐘設置自己的定時器并獨立更新。在React應用程序中,組件是有狀态還是無狀态被視為可能随時間更改的組件的實現細節。 您可以在有狀态組件内使用無狀态組件,反之亦然。

 

上一篇: 使用Node實現Http代理

下一篇: React從入門到精通系列之(6)事件處理

http://m.juhua743465.cn|http://wap.juhua743465.cn|http://www.juhua743465.cn||http://juhua743465.cn