「前端發動機」深刻 React hooks — useEffect

前言

React Hooks的基本用法,官方文檔 已經很是詳細。本文的目的,是想經過一個簡單的例子詳細分析一些使人疑惑的問題及其背後的緣由。這是系列的第二篇,主要講解 useEffect。javascript

我的博客地址 🍹🍰 fe-codehtml

類生命週期

官方文檔中說,能夠將 useEffect 的回調和清理反作用的機制,類比成 class 組件中的生命週期。不過,因爲 class 組件和函數組件自身特性不一樣的緣由,致使這種類比也容易令人迷惑。前端

若是你熟悉 React class 的生命週期函數,你能夠把 useEffect Hook 看作 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個函數的組合。 --使用 Effect Hookjava

不過有時也容易出問題,就像咱們一開始的定時器例子同樣。react

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            setCount(count + 1);
        }, 1000);
    }, []);

    return <h1>{count}</h1>;
}
複製代碼

咱們的需求很明確,就是在 componentDidMount 的時候,設置一個定時器。而且保證不會每次更新(componentDidUpdate)都從新設置。因此咱們把第二個參數設置成[],來達到同樣的效果。git

固然這是有問題的,因爲函數式組件執行方式的不一樣,咱們在 useEffect 中拿到的 count 是閉包引用的,而每次更新又會是一個全新的執行上下文。這在上一篇文章中已經詳細分析過。可是在 class 組件中,生命週期中的引用是這樣的 this.state.count,並且不一樣於函數式,這種方式每次拿到的 count 都是最新的。github

React Hooks 也提供了一個相似做用的 hook 來幫咱們保存一些值 — useRef它能夠很方便地保存任何可變值,其相似於在 class 中使用實例字段的方式。不過這裏不太適用。web

總的來講,useEffect 和真正的生命週期仍是有些區別的,在使用的時候須要多加註意。數組

依賴

經過第一篇文章,咱們已經瞭解了一些很重要的信息,好比:每次更新都是一次從新執行。這不只僅是對於 useState 來講的,整個函數組件都是這樣。不太瞭解的同窗,能夠先閱讀一下 深刻 React hooks — useState瀏覽器

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            setCount(count + 1);
        }, 1000);
    }, []);

    return <h1>{count}</h1>;
}
複製代碼

咱們知道 每次更新都是一次從新執行。咱們給 useEffect 的第二個參數傳的是 [],因此能夠達到回調只運行一次的效果(只設置一次定時器)。

可是咱們更應該知道的是,回調函數只運行一次,並不表明 useEffect 只運行一次。在每次更新中,useEffect 依然會每次都執行,只不過由於傳遞給它的數組依賴項是空的,致使 React 每次檢查的時候,都沒有發現依賴的變化,因此不會從新執行回調。

檢查依賴,只是簡單的比較了一下值或者引用是否相等

並且上面的寫法,官方是不推薦的。咱們應該確保 useEffect 中用到的狀態(如:count ),都完整的添加到依賴數組中。 無論引用的是基礎類型值、仍是對象甚至是函數

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            setCount(count + 1);
            // 不想用到外部狀態能夠用 setCount(count => count + 1);
        }, 1000);
    }, [count]); // 確保全部狀態依賴都放在這裏
    console.log(count);
    return <h1>{count}</h1>;
}
複製代碼

這樣才能保證回調中能夠每次拿到當前的 count 值。

反作用

咦!好像有什麼奇怪的東西。

hook.gif

發生了什麼不得了的事???

994b6f2egy1g2b7msjhbyg207i07idi3.gif

如今想一想咱們都幹了什麼。

  • useEffect 回調裏放了個定時器。
  • 依賴數組按要求寫了 count。
  • 每次 count 改變引發的更新也會同時運行 useEffect 的回調。
  • 回調裏的定時器也會從新設置。
  • 嗯,好像發現問題了。

每次更新時,會從新運行 useEffect 的回調函數,也就會從新設置一個定時器。可是有一個問題是,咱們上一次設置的定時器並無清理掉,因此頻繁的更新會致使愈來愈多的定時器同時在運行。 爲了解決上面的問題,就須要用到 useEffect 的另外一個特性:清除反作用。

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            setCount(count + 1);
        }, 1000);
        // 返回一個清理反作用的函數
        return () => {
            clearInterval(id);
        }
    }, [count]);
    console.log(count);
    return <h1>{count}</h1>;
}
複製代碼

ok,世界安靜了。

那麼,再思考個問題吧。useEffect 清理反作用的時機是何時?在下一次視圖更新以前嗎?

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            console.log(1, '我是定時器', count);
            setCount(count + 1);
        }, 1000);
        return () => {
            console.log(2, `我清理的是 ${count} 的反作用`);
            clearInterval(id);
        }
    }, [count]);
    console.log(3, '我是渲染', count);
    return <h1>{count}</h1>;
}
複製代碼

上面代碼的打印順序會是 一、二、3 嗎?

image.png

顯然不是,useEffect 在視圖更新以後才清理上一次的反作用。這麼處理其實也是和 useEffect 的特性相契合的。React 只會在瀏覽器繪製後運行 useEffect。因此 Effect 的清除一樣被延遲了。上一次的 Effect 會在從新渲染後被清除。

小結

使用 useEffect 時,須要注意狀態的引用,依賴的添加以及反作用的清除(沒有就不用了)。不少時候還須要藉助其餘的 hook 才能完成這個工做,好比 useRef/useCallback等。

參考文章

交流羣

微信羣:掃碼回覆加羣。

mmqrcode1566432627920.png

後記

若是你看到了這裏,且本文對你有一點幫助的話,但願你能夠動動小手支持一下做者,感謝🍻。文中若有不對之處,也歡迎你們指出,共勉。好了,又耽誤你們的時間了,感謝閱讀,下次再見!

感興趣的同窗能夠關注下個人公衆號 前端發動機,好玩又有料。

相關文章
相關標籤/搜索