# React 的生命週期 ###### tags: `React` #### `constructor`要放`props`,務必做好做滿 ![](https://i.imgur.com/NAyk62M.png) > [官方指南](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) > [正確掌握React生命週期](https://zhuanlan.zhihu.com/p/24926575) ## 小書內容補充: 我們把**React.js將組件渲染,並且構造DOM元素然後塞入頁面的過程稱為組件的掛載**(這個定義請好好記住)。其實React.js內部對待每個組件都有這麼一個過程,也就是初始化組件->掛載到頁面上的過程。所以你可以理解一個組件的方法調用是這麼一個過程: ```javascript= -> constructor() -> render() // 然后构造 DOM 元素插入页面, 其實就是我return裡面寫的內容 ``` 這當然是很好理解的。React.js 為了讓我們能夠更好的掌控組件的掛載過程,往上面插入了兩個方法: ```javascript= -> constructor() -> componentWillMount() -> render() // 然后构造 DOM 元素插入页面 -> componentDidMount() ``` 掛載的時候,React.js會在組件的render之前調用`componentWillMount`,在DOM元素塞入頁面以後調用`componentDidMount`。 ## Constructor的作用 ### 示範1: 不讓子元素,產生出`Dom`, 所以在父元素就已經定義好,若是等於true就render出 `<Title />`, 若不是就不render. ``` `{isShow && <Title />}` ``` ```javascript= class Title extends React.Component{ constructor(props) { super(props) console.log('Title created') } render() { return ( <h1>Hello React</h1> ) } } class App extends React.Component { constructor(props){ super(props) this.state={ isShow: true } } render() { console.log('App created') const {isShow} = this.state return ( <div> {isShow && <Title />} <button onClick={()=>{ this.setState({ isShow: !this.state.isShow }) }}>toggle</button> </div> ) } } ``` ## 示範二:(透過CSS) ```javascript= class Title extends React.Component{ constructor(props) { super(props) console.log('Title created') } render() { const {isShow} = this.props return ( <h1 style={{display: isShow ? 'block': 'none'}}>Hello React</h1> ) } } class App extends React.Component { constructor(props){ super(props) this.state={ isShow: true } } render() { console.log('App created') const {isShow} = this.state return ( <div> {/* {isShow && <Title />} */} <Title isShow={isShow}/> <button onClick={()=>{ this.setState({ isShow: !this.state.isShow }) }}>toggle</button> </div> ) } } ``` ## shouldComponentUpdate 示範範例 > 決定要不要call render function, 實際上會用到檢查機制上. 加了不一定效能會變好, 要衡量這個結果, 做優化沒有那麼容易. 從底下程式碼可以看出, 我的render裡面有`<Title>`這個`component` 但是問題是我點選`button`,數字改變,但是因為render裡面有`<Title>`這個`component`,所以他又會被render一次, 因此加上一個機制, 如果下一個傳進來的值,和現在的不相等, 我就不render了. --- immutable Data => 是為了shouldComponetUpdate而存在 => 這樣才能夠比較前後資料是否相同. --- ==注意== 比較有分成兩種, shallow compare vs deep compare ```javascript= var a = { a: 1 } var b = { a: 1 } shallow compare => false, deep compare => true ``` ```javascript= class Title extends React.Component{ // 關鍵程式碼 // shouldComponentUpdate(nextProps){ // if(nextProps !== this.props.title){ // return true // } // return false // } render() { console.log('Title render') return ( <h1>{this.props.title}</h1> ) } } class App extends React.Component { constructor(props){ super(props) this.state={ number: 1 } } // 決定要不要call render function, 實際上會用到檢查機制上. // shouldComponentUpdate(nextProps, nextState){ // console.log(nextState) // if(nextState.number % 2 === 0){ // return false // } // return true // } render() { const {number} = this.state return ( <div> <h1>{this.state.number}</h1> <Title title={'hello world'} /> <button onClick={()=>{ this.setState({ number: this.state.number + 1 }) }}>click me</button> </div> ) } } ``` 加上changeTitle這個button,讓大夥更容易理解 ```javascript= class Title extends React.Component{ shouldComponentUpdate(nextProps){ if(nextProps.title !== this.props.title){ return true } return false } render() { console.log('Title render') return ( <h1>{this.props.title}</h1> ) } } class App extends React.Component { constructor(props){ super(props) this.state={ title: 'hello world', number: 1 } } render() { const {number, title} = this.state return ( <div> <h1>{this.state.number}</h1> <Title title={title} /> <button onClick={()=>{ this.setState({ number: this.state.number + 1 }) }}>click me</button> <button onClick={() => { this.setState({ title: Math.random() }) }}>change title</button> </div> ) } } ``` ## componentDidMount && componentWillMount && componentWillUnmount ### 小書 這邊先來補充下小書的`componentWillMount` && `componentWillUnmount` 其實例子和老師舉的差不多, 只是這邊是用`timer`來做顯示和隱藏 奇怪要我自己做開關, 我還有點忘記, 明天再來複習一下. 我們一般會把組件的`state` 的初始化工作放在`constructor`裡面去做; 在`componentWillMount`進行組件的啟動工作,例如Ajax數據拉取、定時器的啟動; ==(注意, 在16.3版本後`componentWillMount`已經被`componentDidMount`取代)== 組件從頁面上銷毀的時候,有時候需要一些數據的清理,例如定時器的清理,就會放在`componentWillUnmount`裡面去做。 ```javascript= class Clock extends React.Component{ constructor() { super() this.state = { date: new Date() } } componentWillMount() { this.timer = setInterval(() => { this.setState({date: new Date()}) }, 1000); } componentWillUnMount() { clearInterval(this.timer) } render(){ return( <div> <p>現在時間是</p> {this.state.date.toLocaleTimeString()} </div> ) } } class App2 extends React.Component{ constructor(){ super() this.state ={ isShow: true } this.handleShow = this.handleShow.bind(this) } handleShow(){ this.setState({ isShow: !this.state.isShow }) } render(){ const {isShow} = this.state return( <div> {isShow ? <Clock />: null} <button onClick={this.handleShow}>按鈕</button> </div> ) } } ``` --- 底下範例是在說明按了button後, title會被隱藏. 並且同時在call了一個cb, 在2秒後會在更改值為`ya` 而在隱藏時, 因為在`DOM`找不到`title`這個`component` 所以會報錯, 這個時候`componentWillMount` 就出現了, 來清除這個這個在 `componentDidMount`的兩秒假設. > 通常這兩個function是會成對出現的. ```javascript= class Title extends React.Component{ constructor(props){ super(props) this.state = { title: 'hello' } } //在render完後才call這個function. //在剛才建立的時候會被建立, 這時候還沒有render. componentDidMount(){ const timer = setTimeout(() => { this.setState({ title: 'ya!!' }) }, 2000); } //做一些清除垃圾的事情 componentWillMount(){ clearTimeout(this.timer) } //通常這兩個都是成對出現的 render() { const {title} = this.state return ( <h1>{title}</h1> ) } } class App extends React.Component { constructor(props){ super(props) this.state={ showTitle: true } } render() { const {showTitle} = this.state return ( <div> <button onClick={()=>{ this.setState({ showTitle: !this.state.showTitle }) }}>toggle</button> {showTitle && < Title/>} </div> ) } } ``` ## componentDidUpdate 簡單來講, 就是用來判斷state是否改變了? ```javascript= //底下是我原本的寫法,其實蠻笨的, 因為重複很多component. class Apple extends React.Component{ constructor(){ super() this.state={ counter:1 } } render(){ const {counter} = this.state return( <div>apple: {counter} <button onClick={()=>{ this.setState({ counter: this.state.counter + 1 }) }}>+1</button> </div> ) } } class Orange extends React.Component { constructor() { super() this.state = { counter: 1 } } render() { const { counter } = this.state return ( <div>orange: {counter} <button onClick={() => { this.setState({ counter: this.state.counter + 1 }) }}>+1</button> </div> ) } } class Banana extends React.Component { constructor() { super() this.state = { counter: 1 } } render() { const { counter } = this.state return ( <div>banana: {counter} <button onClick={() => { this.setState({ counter: this.state.counter + 1 }) }}>+1</button> </div> ) } } class App extends React.Component { render() { return ( <div> <Apple /> <Orange /> <Banana /> </div> ) } } ``` ```javascript= //這邊是老師的寫法, 直接都放在同一個component去做渲染. class App extends React.Component { constructor(props){ super(props) this.state ={ apple: 1, orange: 1, banana: 1 } this.addApple = this.addApple.bind(this) this.addOrange = this.addOrange.bind(this) this.addBanana = this.addBanana.bind(this) this.log = this.log.bind(this) } // react 最核心是在state改變,需求是判斷水果數量有沒有變動,所以直接監測之前的state和現在的state是否有變動是更好的作法 componentDidUpdate(prevProps, prevState){ if (prevState.apple !== this.state.apple || prevState.orange !== this.state.orange || prevState.banana !== this.state.banana ){ this.log() } } log(){ console.log(this.state.apple, this.state.orange, this.state.banana) } //這邊是就有的方法,我變動了state,我來看他log出來的東西 addApple(){ this.setState({ apple: this.state.apple + 1 }, //this.log) //裡面原本有一個cb當作參數, 這邊直接簡化成this.log. } addOrange() { this.setState({ orange: this.state.orange + 1 }//this.log) } addBanana() { this.setState({ banana: this.state.banana + 1 }//this.log) } render() { const {apple, orange, banana} = this.state return ( <div> <div> apple: {apple} <button onClick={this.addApple}>+1</button> </div> <div> orange: {orange} <button onClick={this.addOrange}>+1</button> </div> <div> banana: {banana} <button onClick={this.addBanana}>+1</button> </div> </div> ) } } ```