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

前言

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

我的博客地址 🍹🍰 fe-codehtml

思考

一塊兒來看看這個栗子。前端

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

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

咱們指望,useEffect 只執行一次,且後續每隔 1s,count 自動 + 1。然而, 實際上 count 從 0 到 1 後,再沒有變化,一直都是 1。難道是 setInterval 沒執行?因而咱們很疑惑的加上了打印。java

image.png

事實是,setInterval 每次執行的時候,拿到的 count 都是 0。很天然的咱們會想到閉包,可是閉包能徹底解釋這個現象嗎。咱們稍加修改再看下這個例子。react

function Counter() {
    const [count, setCount] = useState(0);
    let num = 0;
    useEffect(() => {
        const id = setInterval(() => {
            // 經過 num 來給 count 提供值
            console.log(num);
            setCount(++num);
        }, 1000);
    }, []);

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

image.png

咱們能夠看到,藉助 num 這個中間變量,咱們能夠獲得想要的結果。可是,一樣是閉包,爲何 num 就能記住以前的值呢?其實問題出在 count 上,繼續往下看:git

function Counter() {
    // ...
    console.log('我是 num', num);

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

image.png

渲染的 num 和定時器中的 num 爲何會不同呢?github

每次都是從新執行

到這裏我想說的究竟是什麼呢?咱們能夠清晰看見渲染出的 num 和 setInterval 中的 num,是不一樣的。這是由於在 React 中,對於函數式組件來說,每次更新都會從新執行一遍函數。也就是說,每次更新都會在當前做用域從新聲明一個 let num = 0,因此,定時器中閉包引用的那個 num,和每次更新時渲染的 num,根本不是同一個。固然,咱們能夠很輕易的把它們變成同一個。數組

let num = 0; // 將聲明放到渲染組件外面
function Counter() {
    // ...
    return <h1>{count}-----{num}</h1>;
}
複製代碼

嗯,說了這麼多,跟 count 有什麼關係呢?同理,正由於函數組件每次都會總體從新執行,那麼 Hooks 固然也是這樣。微信

function Counter() {
    const [count, setCount] = useState(0);
    // ...
}
複製代碼

useState 應該理解爲和普通的 javascript 函數同樣,而不是 React 的什麼黑魔法。函數組件更新的時候,useState 會從新執行,對應的,也會從新聲明 [count, setCount] 這一組常量。只不過 React 對這個函數作了一些特殊處理。好比:首次執行時,會將 useState 的參數初始化給 count,而之後再次執行時,則會直接取上次 setCount (若是有調用) 賦過的值(React 經過某種方式保存起來的)。閉包

有了這個概念,就不難知道,定時器裏的setCount(count + 1) ,這個 count 和每次更新從新聲明的 count,也是徹底不一樣的兩個常量,只不過它們的值,可能會相等。

好比,咱們嘗試把以前的 num,直接用 count 替代。

function Counter() {
    // 注意這裏變成 let
    let [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            // 這種寫法是很差的
            setCount(++count);
        }, 1000);
    }, []);
    console.log(count);
    return <h1>{count}</h1>;
}
複製代碼

這時候不管是打印仍是頁面表現都和你指望的同樣,可是這違背了 React 的原則,並且也讓程序變得更讓人迷惑。也就致使你並不能清楚地知道:此時渲染的 count 和 setInterval 中的 count 已經不是同一個了。儘管他們的值是相等的。

固然,這種場景下 React 也提供了可行的方法,可以每次拿到 count 的最新值,就是給 setCount 傳遞一個回調函數。

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            // 注意:這裏變成回調了
            setCount(count => count + 1);
        }, 1000);
    }, []);

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

執行圖解

回過頭再看看開始的例子:

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

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

image.png

小結

count 每次都被從新聲明瞭,setInterval 由於 useEffect 設置了只執行一次的緣故,在第一次更新時閉包引用的 count 始終是 0,後續更新的 count 和它不要緊。

交流羣

微信羣:掃碼回覆加羣。

mmqrcode1566432627920.png

後記

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

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

相關文章
相關標籤/搜索