• QQ咨詢:4001806960
  • 咨詢熱線:400-180-6960

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

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

 

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)事件處理