避免已卸載的組件繼續(xù)調(diào)用setState()

本文轉(zhuǎn)載自我的個人博客

這是一篇譯文,原文在這里。有興趣的同學(xué)可以直接閱讀原文,寫的很好。圖省事的同學(xué)可以直接看我的精簡的譯文。

相信很多react初學(xué)者都遇到過以下兩個警告(warnings)

  • Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.

  • Warning: Can’t call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

一般來說警告并不會讓你的程序崩潰,但是它會降低程序的性能,比如我們上面提到的這兩個警告就是這樣。下面讓我們來討論一下這兩個警告到底講的是什么吧。

當(dāng)一個已經(jīng)被卸載(unmounted)的組件調(diào)用setState()方法時,你就會遇到以上兩個警告。一般來說,有兩種情況會導(dǎo)致組件的卸載:

  1. 通過條件渲染的組件,其渲染條件從達(dá)到變?yōu)闆]有達(dá)到,導(dǎo)致已渲染的組件的卸載
  2. 通過路由手段路由到別的組件,導(dǎo)致原組件被卸載

當(dāng)組件被卸載后,被卸載的組件內(nèi)的某些異步邏輯可能在組件被卸載后調(diào)用了setState()企圖更新組件內(nèi)的state,通常有三種場景可能會發(fā)生這種情況:

  1. 你對某個API提交了一個異步請求,組件在收到響應(yīng)前就被卸載了,收到響應(yīng)之后調(diào)用了setState()企圖更新state,但這個時候組件早就被卸載了。
  2. 你為組件添加了監(jiān)聽事件,但是沒有在componentWillUnmount()里移除這個監(jiān)聽事件,當(dāng)組件移除以后監(jiān)聽的事件被觸發(fā)。
  3. 你在類似于setInterval()的函數(shù)里調(diào)用了setState(),但是沒有在componentWillUnmount()里移除這些函數(shù)。

那么在遇到以上兩個警告時我們怎么樣才能解決他們呢?

避免 intervals/listeners 在已卸載的組件中調(diào)用 setState()

對于上面提到的三種情況中的后兩種情況,我們只需要在componentWillUnmount()生命周期鉤子里將intervals/listeners移除就可以了。具體操作可以參考這個例子

避免異步請求在已卸載的組件中調(diào)用 setState()

為了避免異步請求在已卸載的組件中調(diào)用setState(),我們可以在請求收到響應(yīng)后添加一個判斷條件,判斷此時組件有沒有被卸載,如果沒有卸載再繼續(xù)執(zhí)行之后的代碼(比如setState())。具體實現(xiàn)如下:

  class News extends Component {
    //添加一個 class field 記錄組件有沒有被卸載,初始化為false,表示已卸載
    _isMounted = false;

    constructor(props) {
      super(props);

      this.state = {
        news: [],
      };
    }

    componentDidMount() {
      //組件在被掛載后將_isMounted的值更新為true,表示已掛載
      this._isMounted = true;

      axios
        .get('https://hn.algolia.com/api/v1/search?query=react')
        .then(result => {
          //通過_isMounted判斷組件有沒有被卸載
          if (this._isMounted) {
            this.setState({
              news: result.data.hits,
            });
          }
        });
    }

    componentWillUnmount() {
      //在組件被卸載時將_isMounted更新為false,表示組件已卸載
      this._isMounted = false;
    }

    render() {
      ...
    }
  }

一個小插曲,關(guān)于class field是什么,給大家兩個例子,可以先體會下用class field和不用的區(qū)別,以后有時間,我再寫一篇專門關(guān)于class field的文章。

不用class field:

  class IncreasingCounter {
    constructor() {
      this._count = 0;
    }
    get value() {
      console.log('Getting the current value!');
      return this._count;
    }
    increment() {
      this._count++;
    }
  }

class field:

  class IncreasingCounter {
    _count = 0;
    get value() {
      console.log('Getting the current value!');
      return this._count;
    }
    increment() {
      this._count++;
    }
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容