<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【React Hooks 專題】useEffect 使用指南

          共 7526字,需瀏覽 16分鐘

           ·

          2021-07-20 17:31


          引言

          Hooks 是 React 16.8 的新增特性,至今經(jīng)歷兩年的時(shí)間,它可以讓你在不編寫(xiě) class 組件的情況下使用 state 以及其他 React 特性。useEffect 是基礎(chǔ) Hooks 之一,我在項(xiàng)目中使用較為頻繁,但總有些疑惑 ,比如:

          • 如何正確使用 useEffect
          • useEffect 的執(zhí)行時(shí)機(jī) ?
          • useEffect 和生命周期的區(qū)別 ?

          本文主要從以上幾個(gè)方面分析 useEffect ,以及與另外一個(gè)看起來(lái)和 useEffect 很像的 Hook useLayoutEffect 的使用和它們之間的區(qū)別。

          useEffect 簡(jiǎn)介

          首先介紹兩個(gè)概念,純函數(shù)和副作用函數(shù)。純函數(shù)( Pure Function ):對(duì)于相同的輸入,永遠(yuǎn)會(huì)得到相同的輸出,而且沒(méi)有任何可觀察的副作用,這樣的函數(shù)被稱為純函數(shù)。副作用函數(shù)( Side effect Function ):如果一個(gè)函數(shù)在運(yùn)行的過(guò)程中,除了返回函數(shù)值,還對(duì)主調(diào)用函數(shù)產(chǎn)生附加的影響,這樣的函數(shù)被稱為副作用函數(shù)。useEffect 就是在 React 更新 DOM 之后運(yùn)行一些額外的代碼,也就是執(zhí)行副作用操作,比如請(qǐng)求數(shù)據(jù),設(shè)置訂閱以及手動(dòng)更改 React 組件中的 DOM 等。

          正確使用 useEffect

          基本使用方法:useEffect(effect)根據(jù)傳參個(gè)數(shù)和傳參類型,useEffect(effect) 的執(zhí)行次數(shù)和執(zhí)行結(jié)果是不同的,下面一一介紹。

          • 默認(rèn)情況下,effect 會(huì)在每次渲染之后執(zhí)行。示例如下:
          useEffect(() => {
            const subscription = props.source.subscribe();
            return () => {
              // 清除訂閱
              subscription.unsubscribe();
            };
          });
          • 也可以通過(guò)設(shè)置第二個(gè)參數(shù),依賴項(xiàng)組成的數(shù)組  useEffect(effect,[]) ,讓它在數(shù)組中的值發(fā)生變化的時(shí)候執(zhí)行,數(shù)組中可以設(shè)置多個(gè)依賴項(xiàng),其中的任意一項(xiàng)發(fā)生變化,effect 都會(huì)重新執(zhí)行。示例如下:
          useEffect(
            () => {
              const subscription = props.source.subscribe();
              return () => {
                subscription.unsubscribe();
              };
            },
            [props.source],
          );

          需要注意的是:當(dāng)依賴項(xiàng)是引用類型時(shí),React 會(huì)對(duì)比當(dāng)前渲染下的依賴項(xiàng)和上次渲染下的依賴項(xiàng)的內(nèi)存地址是否一致,如果一致,effect 不會(huì)執(zhí)行,只有當(dāng)對(duì)比結(jié)果不一致時(shí),effect 才會(huì)執(zhí)行。示例如下:

          function Child(props{
            
            useEffect(() => {
              console.log("useEffect");
            }, [props.data]);
            
            return <div>{props.data.x}</div>;
          }

          let b = { x1 };

          function Parent({
            const [count, setCount] = useState(0);
            console.log("render");
            return (
              <div>
                <button
                  onClick={() =>
           {
                    b.x = b.x + 1;
                    setCount(count + 1);
                  }}
                >
                  Click me
                </button>
                <Child data={b} />
              </div>

            );
          }

          結(jié)果如下:


          上面實(shí)例中,組件 <Child/> 中的 useEffect 函數(shù)中的依賴項(xiàng)是一個(gè)對(duì)象,當(dāng)點(diǎn)擊按鈕對(duì)象中的值發(fā)生變化,但是傳入 <Child/>  組件的內(nèi)存地址沒(méi)有變化,所以 console.log("useEffect") 不會(huì)執(zhí)行,useEffect 不會(huì)被打印。為了解決這個(gè)問(wèn)題,我們可以使用對(duì)象中的屬性作為依賴,而不是整個(gè)對(duì)象。把上面示例中組件 <Child/> 修改如下:

          function Child(props{
            
            useEffect(() => {
              console.log("useEffect");
            }, [props.data.x]);
            
            return <div>{props.data.x}</div>;
          }

          修改后結(jié)果如下:


          可見(jiàn) useEffect 函數(shù)中的 console.log("useEffect") 被執(zhí)行,打印出 useEffect。

          • 當(dāng)依賴項(xiàng)是一個(gè)空數(shù)組 [] 時(shí) , effect 只在第一次渲染的時(shí)候執(zhí)行。

          useEffect 的執(zhí)行時(shí)機(jī)

          默認(rèn)情況下,effect 在第一次渲染之后和每次更新之后都會(huì)執(zhí)行,也可以是只有某些值發(fā)生變化之后執(zhí)行,重點(diǎn)在于是每輪渲染結(jié)束后延遲調(diào)用( 異步執(zhí)行 ),這是 useEffect 的好處,保證執(zhí)行 effect 的時(shí)候,DOM 都已經(jīng)更新完畢,不會(huì)阻礙 DOM 渲染,造成視覺(jué)阻塞。

          useEffect 和 useLayoutEffect 的區(qū)別

          useLayoutEffect 的使用方法和 useEffect 相同,區(qū)別是他們的執(zhí)行時(shí)機(jī)。

          如上面所說(shuō),effect 的內(nèi)容是會(huì)在渲染 DOM 之后執(zhí)行,然而并非所有的操作都能被放在 effect 都延遲執(zhí)行的,例如,在瀏覽器執(zhí)行下一次繪制前,需要操作 DOM 改變頁(yè)面樣式,如果放在 useEffect 中執(zhí)行,會(huì)出現(xiàn)閃屏問(wèn)題。而 useLayoutEffect 是在瀏覽器執(zhí)行繪制之前被同步執(zhí)行,放在 useLayoutEffect 中就會(huì)避免這個(gè)問(wèn)題。

          這篇文章中可以清楚的看到上述例子的具體實(shí)現(xiàn):useEffect 和 useLayoutEffect 的區(qū)別

          對(duì)比 useEffect 和生命周期

          如果你熟悉生命周期函數(shù),你可能會(huì)用生命周期的思路去類比思考 useEffect 的執(zhí)行過(guò)程,但其實(shí)并不建議這么做,因?yàn)?useEffect 的心智模型和 componentDidMount 等其他生命周期是不同的。

          Function 組件中不存在生命周期,React 會(huì)根據(jù)我們當(dāng)前的 props 和 state 同步 DOM ,每次渲染都會(huì)被固化,包括 state、props、side effects 以及寫(xiě)在 Function 組件中的所有函數(shù)。

          另外,大多數(shù) useEffect 函數(shù)不需要同步執(zhí)行,不會(huì)像 componentDidMountcomponentDidUpdate 那樣阻塞瀏覽器更新屏幕。

          所以 useEffect 可以被看作是每一次渲染之后的一個(gè)獨(dú)立的函數(shù) ,可以接收 props 和 state ,并且接收的 props 和 state 是當(dāng)次 render 的數(shù)據(jù),是獨(dú)立的 。相對(duì)于生命周期 componentDidMount 中的 this.state 始終指向最新數(shù)據(jù), useEffect 中不一定是最新的數(shù)據(jù),更像是渲染結(jié)果的一部分 —— 每個(gè) useEffect 屬于一次特定的渲染。對(duì)比示例如下:

          • 在 Function 組件中使用  useEffect  代碼示例 (點(diǎn)擊在線測(cè)試):
          function Counter({
            const [count, setCount] = useState(0);

            useEffect(() => {
              setTimeout(() => {
                console.log(`You clicked ${count} times`);
              }, 3000);
            });

            return (
              <div>
                <p>You clicked {count} times</p>
                <button onClick={() => setCount(count + 1)}>
                  Click me
                </button>
              </div>

            );
          }

          結(jié)果如下:


          • 在 Class 組件中的使用生命周期,代碼示例:
            componentDidUpdate() {
              setTimeout(() => {
                console.log(`You clicked ${this.state.count} times`);
              }, 3000);
            }

          結(jié)果如下:


          但是每次渲染之后都去執(zhí)行 effect 并不高效。所以怎么解決呢 ?這就需要我們告訴 React 對(duì)比依賴來(lái)決定是否執(zhí)行 effect

          如何準(zhǔn)確綁定依賴

          effect 中用到了哪些外部變量,都需要如實(shí)告訴 React ,那如果沒(méi)有正確設(shè)置依賴項(xiàng)會(huì)怎么樣呢 ?示例如下 :


          上面例子中, useEffect 中用到的依賴項(xiàng) count,卻沒(méi)有聲明在卸載依賴項(xiàng)數(shù)組中,useEffect 不會(huì)再重新運(yùn)行(只打印了一次 useEffect ), effectsetInterVal 拿的 count 始終是初始化的 0 ,它后面每一秒都會(huì)調(diào)用 setCount(0 + 1) ,得到的結(jié)果始終是 1 。下面有兩種可以正確解決依賴的方法:

          1.在依賴項(xiàng)數(shù)組中包含所有在 effect 中用到的值

          effect 中用到的外部變量 count 如實(shí)添加到依賴項(xiàng)數(shù)組中,結(jié)果如下:


          可以看到依賴項(xiàng)數(shù)組是正確的,并且解決了上面的問(wèn)題,但是也可以發(fā)現(xiàn),隨之帶來(lái)的問(wèn)題是:定時(shí)器會(huì)在每一次 count 改變后清除和重新設(shè)定,重復(fù)創(chuàng)建/銷毀,這不是我們想要的結(jié)果。

          2.第二種方法是修改 effect 中的代碼來(lái)減少依賴項(xiàng)

          即修改 effect 內(nèi)部的代碼讓 useEffect 使得依賴更少,需要一些移除依賴常用的技巧,如:setCount 還有一種函數(shù)回調(diào)模式,你不需要關(guān)心當(dāng)前值是什么,只要對(duì) “舊的值” 進(jìn)行修改即可,這樣就不需要通過(guò)把 count 寫(xiě)到依賴項(xiàng)數(shù)組這種方式來(lái)告訴 React 了,因?yàn)?React 已經(jīng)知道了。


          是否需要清除副作用

          若只是在 React 更新 DOM 之后運(yùn)行一些額外的代碼,比如發(fā)送網(wǎng)絡(luò)請(qǐng)求,手動(dòng)變更 DOM,記錄日志,無(wú)需清除操作,因?yàn)閳?zhí)行之后就可以被忽略。

          需要清除的是指那些執(zhí)行之后還有后續(xù)的操作,比如說(shuō)監(jiān)聽(tīng)鼠標(biāo)的點(diǎn)擊事件,為防止內(nèi)存泄漏清除函數(shù)將在組件卸載之前調(diào)用,可以通過(guò) useEffect 的返回值銷毀通過(guò) useEffect 注冊(cè)的監(jiān)聽(tīng)。

          清除函數(shù)執(zhí)行時(shí)機(jī)是在新的渲染之后進(jìn)行的,示例如下(點(diǎn)擊在線測(cè)試):

          const Example = () => {
            const [count, setCount] = useState(0);

            useEffect(() => {
              console.log("useEffect");
              return () => {
                console.log("return");
              };
            }, [count]);

            return (
              <div>
                <p>You Click {count} times </p>
                {console.log("dom")}
                <button
                  onClick={() =>
           {
                    setCount(count + 1);
                  }}
                >
                  Click me
                </button>
              </div>

            );
          };

          結(jié)果如下:


          需要注意的是useEffect 的清除函數(shù)在每次重新渲染時(shí)都會(huì)執(zhí)行,而不是只在卸載組件的時(shí)候執(zhí)行 。

          參考文檔

          React Core Team 成員、Readux 作者 Dan 對(duì) useEffect 的完全解讀  ---  A Complete Guide to useEffect


          關(guān)于作者

          Starry , Web 前端工程師,就職于民生銀行后端平臺(tái)研發(fā)團(tuán)隊(duì),螢火蟲(chóng)實(shí)驗(yàn)室成員,目前負(fù)責(zé)仿真服務(wù)平臺(tái)前端開(kāi)發(fā)工作。



          瀏覽 76
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品传媒一区二区三区 | 成人无码在线免费观看 | 最黄色视频久久 | 91久久精品无码一区二区三区 | 人体体内射精一区二区 |