iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 18
0
Modern Web

給初入JS框架新手的React.js入門系列 第 18

【React.js入門 - 18】 React生命週期(3/4): Unmount - 只有componentWillUnmount

  • 分享至 

  • xImage
  •  

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問


componentWillUnmount是在元件被移除時所會呼叫一次的唯一生命週期函數。

欸欸欸,為什麼我們才剛把元件生完,你就要直接快轉到他死去啊?

因為就跟講一個人的人生一樣,講他怎麼生跟死一定比講他人生中做了那些豐功偉業還要快。
因為componentWillUnmount很常跟前一篇的componentDidMount一起使用,所以先來講它。

componentWillUnmount的功用跟c-like物件中的Destructor很像,最常被用來移除componentDidMount中「只想要隨著元件新增的一切東西」。

為什麼「新增東西」要在componentDidMount中做? 又為什麼要搭配componentWillUnmount?

由於

  1. Mount只會執行一次,可以避免重複執行。
  2. 在componentDidMount中做,可以避免無法操作在render中return的元素的問題。
  3. 在過往也能避免componentWillMount重複執行的bug。

所以我們一般在componentDidMount中做。
那為什麼要搭配componentWillUnmount呢?我們來看這個範例:
index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App'
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
    <div>
        <App/>
        <div id="talk"></div>
    </div>,
    document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

App.js

import React, { Component } from 'react';
import Baby from './Baby'

class App extends Component{
  constructor(props) {
    super(props);
    this.state={
      rate:"",
      isBorn:true
    }
    this.handleClick=this.handleClick.bind(this);
    this.spawnBaby=this.spawnBaby.bind(this);
  }

  handleClick(){
    this.setState({isBorn:!this.state.isBorn})
  }

  spawnBaby(){
    if(this.state.isBorn)
      return <Baby/>
  }

    render(){
        return(
          <div>
            <button onClick={this.handleClick}>
                {(this.state.isBorn===true)?"讓他回去肚子裡":"讓他生"} 
            </button>
              {this.spawnBaby()}
          </div>
        );
    }
}
export default App;

Baby.js

import React, { Component } from 'react';
class Baby extends Component{
  constructor(props) {
    super(props);
    this.state={
        isRightDad: true,
        isGetData: false,
        Mom: ""
    }
    this.ajaxSimulator=this.ajaxSimulator.bind(this)
  }

    componentWillMount(){
        if(this.props.dad!=="Chang")
            this.setState({isRightDad:false})
    }

    ajaxSimulator(){
        setTimeout(()=>{this.setState({isGetData:true, Mom:"小美"})},3000)
    }

    componentDidMount(){
        this.ajaxSimulator();
        document.getElementById("talk").append("爸!")
    }



    render(){
        if(this.state.isGetData===false)
            return(
                <div id="msg">讀取中</div>
            );
        else
            return(
                <div id="msg">他的媽媽是{this.state.Mom}</div>
            );                
    }
}
export default Baby;

總之做的事情是Baby出生完的時候(componentDidMount())會叫一聲爸,點擊一下按鍵時,Baby就會被塞回肚子裡(?)再點一次就會生出來。按了很多次之後,你發現變成這樣:

注意到了嗎? 「爸!」並沒有隨著Baby被移除而消失,這是因為我們在Baby誕生後更改的元素是index.js中的div,而它並不在Baby.js中,所以並不會隨著Baby被移除而消失。

這樣重複新增的情形大部份的時候不是我們想要的狀況,而是希望他能隨著元素移除而跟著不見,這時候就要搭配componentWillUnmount()作移除的動作。

移除新增的元素

假使我們是這樣新增元素

componentDidMount(){
    document.getElementById("talk").append(
        '<div id="callDad">爸!</div>'
    )
}

移除時就要找到該元素並remove

componentWillUnmount(){
    document.getElementById("callDad").remove();
}

另外在上面的範例中,我們只要使用

componentWillUnmount(){
    document.getElementById("talk").innerHTML="";
}

就能把talk清空

移除監聽事件

監聽事件也是我們很常對DOM做的操作,假使我們今天是這樣監聽視窗的點擊事件:

IWasClick(){
    console.log("啊!我被點了!")
}

componentDidMount(){
    window.addEventListener('mousedown', this.IWasClick)
}

我們就要這樣避免重複監聽:

componentWillUnmount(){
    window.removeEventListener('mousedown',this.IWasClick);
}

移除setInterval

setInterval就不特別講了,原理及方式都和前面這幾個一樣。改成以clearInterval()移除就可以。

小結:

如果你學過其他物件導向語言,應該對componentWillUnmount的使用能更很快理解。
Mount和Unmount講完了,接下來會講Update系列,因為不是每個Update函數都很常用到+有些使用原理跟前面差不多,所以下一篇有些部分會比較快帶過,然後一口氣把Update系列講完。


上一篇
【React.js入門 - 17】 React生命週期(2/4): Mount(下) - 應該多用的componentDidMount
下一篇
【React.js入門 - 19】 React生命週期(4/4): Update系列一次講完
系列文
給初入JS框架新手的React.js入門31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
holuken
iT邦新手 5 級 ‧ 2020-09-02 13:35:07

Andy大大您好,
我照著您此篇的最後修改的範例
componentDidMount(){
document.getElementById("talk").append(
爸!
)
}
但畫面上卻render出[object Object],如下圖
https://ithelp.ithome.com.tw/upload/images/20200902/20130165q4w0WogQFj.jpg
字串能正常work,請教該如何讓那個div能正常render呢?

Andy Chang iT邦研究生 4 級 ‧ 2020-09-02 14:12:56 檢舉

啊那裡應該是我複製code的時候漏掉了,應該要是字串。

componentDidMount(){
    document.getElementById("talk").append(
        '<div id="callDad">爸!</div>'
    )
}

至於為什麼用JSX不用字串的時候會變成Object,原因是這樣的:

  1. 當Babel在程式碼找到JSX的時候,會自動把JSX用React.createElement轉成「專給React virtual DOM去比對是否要更新DOM用的物件」
  2. 本來這樣是沒問題,但我上面的程式碼把JSX直接丟到DOM元素上面了,DOM並不認得React的格式,對DOM而言這就只是一個JS物件而已,所以它就變成Object

理論上這裡比較好的寫法應該是全部改成用DOM api來寫:

componentDidMount(){
    let dadElement = document.createElement('div');
    dadElement.setAttribute('id','callDad');
    dadElement.textContent = "爸!";
    document.getElementById("talk").append(dadElement)
}

不過這裡只是想示意說如果逼不得已要直接操作DOM的時候要在這邊做而已,能用React的方法就用React吧!

holuken iT邦新手 5 級 ‧ 2020-09-02 14:26:33 檢舉

感謝大大,使用DOM api的方式就work了

我要留言

立即登入留言