<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

          共 9451字,需瀏覽 19分鐘

           ·

          2020-01-08 23:25

          0d1dedc89727314e54259552e0df7cd9.webp

          本文是譯文,原文地址是:https://medium.com/@sdolidze/the-iceberg-of-react-hooks-af0b588f43fb

          React Hooks 與類組件不同,它提供了用于優(yōu)化和組合應(yīng)用程序的簡(jiǎn)單方式,并且使用了最少的樣板文件。

          如果沒有深入的知識(shí),由于微妙的 bug 和抽象層漏洞,可能會(huì)出現(xiàn)性能問題,代碼復(fù)雜性也會(huì)增加。

          我已經(jīng)創(chuàng)建了 12 個(gè)案例研究來(lái)演示常見的問題以及解決它們的方法。 我還編寫了 React Hooks RadarReact Hooks Checklist,來(lái)推薦和快速參考。

          案例研究: 實(shí)現(xiàn) Interval

          目標(biāo)是實(shí)現(xiàn)計(jì)數(shù)器,從 0 開始,每 500 毫秒增加一次。 應(yīng)提供三個(gè)控制按鈕: 啟動(dòng)、停止和清除。

          8a7bdeeaf8ebc0f7cba5b5e2c1270815.webp

          Level 0:Hello World

          export default function Level00() {
          console.log('renderLevel00');
          const [count, setCount] = useState(0);
          return (
          <div>
          count => {count}
          <button onClick={() => setCount(count + 1)}>+button>

          <button onClick={() => setCount(count - 1)}>-button>
          div>
          );
          }

          這是一個(gè)簡(jiǎn)單的、正確實(shí)現(xiàn)的計(jì)數(shù)器,用戶單擊時(shí)計(jì)數(shù)器的增加或減少。

          Level 1:setInterval

          export default function Level01() {
          console.log('renderLevel01');
          const [count, setCount] = useState(0);
          setInterval(() => {
          setCount(count + 1);
          }, 500);
          return <div>count => {count}div>;
          }

          此代碼的目的是每 500 毫秒增加計(jì)數(shù)器。 這段代碼存在巨大的內(nèi)存泄漏并且實(shí)現(xiàn)不正確。 它很容易讓瀏覽器標(biāo)簽崩潰。 由于 Level01 函數(shù)在每次渲染發(fā)生時(shí)被調(diào)用,所以每次觸發(fā)渲染時(shí)這個(gè)組件都會(huì)創(chuàng)建新的 interval。

          突變、訂閱、計(jì)時(shí)器、日志記錄和其他副作用不允許出現(xiàn)在函數(shù)組件的主體中(稱為 React 的 render 階段)。 這樣做會(huì)導(dǎo)致用戶界面中的錯(cuò)誤和不一致。

          Hooks API Reference[1]: useEffect[2]

          Level 2:useEffect

          export default function Level02() {
          console.log('renderLevel02');
          const [count, setCount] = useState(0);
          useEffect(() => {
          setInterval(() => {
          setCount(count + 1);
          }, 500);
          });
          return <div>Level 2: count => {count}div>;
          }

          大多數(shù)副作用放在 useEffect 內(nèi)部。 但是此代碼還有巨大的資源泄漏,并且實(shí)現(xiàn)不正確。 useEffect 的默認(rèn)行為是在每次渲染后運(yùn)行,所以每次計(jì)數(shù)更改都會(huì)創(chuàng)建新的 Interval

          Hooks API Reference[3]: useEffect[4], Timing of Effects[5].

          Level 3: 只運(yùn)行一次

          export default function Level03() {
          console.log('renderLevel03');
          const [count, setCount] = useState(0);
          useEffect(() => {
          setInterval(() => {
          setCount(count + 1);
          }, 300);
          }, []);
          return <div>count => {count}div>;
          }

          [] 作為 useEffect 的第二個(gè)參數(shù),將在 mount 之后只調(diào)用一次 function,即使只調(diào)用一次 setInterval,這段代碼的實(shí)現(xiàn)也是不正確的。

          雖然 count 會(huì)從 0 增加到 1,但是不會(huì)再增加,只會(huì)保持成 1。 因?yàn)榧^函數(shù)只被創(chuàng)建一次,所以箭頭函數(shù)里面的 count 會(huì)一直為 0.

          這段代碼也存在微妙的資源泄漏。 即使在組件卸載之后,仍將調(diào)用 setCount。

          Hooks API Reference[6]: useEffect[7], Conditionally firing an effect[8].

          Level 4:清理

          useEffect(() => {
          const interval = setInterval(() => {
          setCount(count + 1);
          }, 300);
          return () => clearInterval(interval);
          }, []);

          為了防止資源泄漏,Hooks 的生命周期結(jié)束時(shí),必須清理所有內(nèi)容。 在這種情況下,組件卸載后將調(diào)用返回的函數(shù)。

          這段代碼沒有資源泄漏,但是實(shí)現(xiàn)不正確,就像之前的代碼一樣。

          Hooks API Reference[9]: Cleaning up an effect[10].

          Level 5:使用 count 作為依賴項(xiàng)

          useEffect(() => {
          const interval = setInterval(() => {
          setCount(count + 1);
          }, 500);
          return () => clearInterval(interval);
          }, [count]);

          useEffect 提供依賴數(shù)組會(huì)改變它的生命周期。 在這個(gè)例子中,useEffectmount 之后會(huì)被調(diào)用一次,并且每次 count 都會(huì)改變。 清理函數(shù)將在每次 count 更改時(shí)被調(diào)用以釋放前面的資源。

          這段代碼工作正常,沒有任何錯(cuò)誤,但是還是有點(diǎn)不好,每 500 毫秒創(chuàng)建和釋放 setInterval, 每個(gè) setInterval 總是調(diào)用一次。

          Hooks API Reference[11]: useEffect[12], Conditionally firing an effect[13].

          Level 6:setTimeout

          useEffect(() => {
          const timeout = setTimeout(() => {
          setCount(count + 1);
          }, 500);
          return () => clearTimeout(timeout);
          }, [count]);

          這段代碼和上面的代碼可以正常工作。 因?yàn)?useEffect 是在每次 count 更改時(shí)調(diào)用的,所以使用 setTimeout 與調(diào)用 setInterval 具有相同的效果。

          這個(gè)例子效率很低,每次渲染發(fā)生時(shí)都會(huì)創(chuàng)建新的 setTimeout,React 有一個(gè)更好的方式來(lái)解決問題。

          Level 7:useState 的函數(shù)更新

          useEffect(() => {
          const interval = setInterval(() => {
          setCount(c => c + 1);
          }, 500);
          return () => clearInterval(interval);
          }, []);

          在前面的例子中,我們對(duì)每次 count 更改運(yùn)行 useEffect,這是必要的,因?yàn)槲覀冃枰冀K保持最新的當(dāng)前值。

          useState 提供 API 來(lái)更新以前的狀態(tài),而不用捕獲當(dāng)前值。 要做到這一點(diǎn),我們需要做的就是向 setState 提供 lambda(匿名函數(shù))。

          這段代碼工作正常,效率更高。 在組件的生命周期中,我們使用單個(gè) setIntervalclearInterval 只會(huì)在卸載組件之后調(diào)用一次。

          Hooks API Reference[14]: useState[15], Functional updates[16].

          Level 8:局部變量

          export default function Level08() {
          console.log('renderLevel08');
          const [count, setCount] = useState(0);
          let interval = null;

          const start = () => {
          interval = setInterval(() => {
          setCount(c => c + 1);
          }, 500);
          };
          const stop = () => {
          clearInterval(interval);
          };
          return (
          <div>
          count => {count}
          <button onClick={start}>startbutton>

          <button onClick={stop}>stopbutton>
          div>
          );
          }

          我們?cè)黾恿?start 和 stop 按鈕。 此代碼實(shí)現(xiàn)不正確,因?yàn)?stop 按鈕不工作。 因?yàn)樵诿看武秩酒陂g都會(huì)創(chuàng)建新的引用(指 interval 的引用),因此 stop 函數(shù)里面 clearInterval 里面的 interval 是 null。

          Hooks API Reference[17]: Is there something like instance variables?[18]

          Level 9:useRef

          export default function Level09() {
          console.log('renderLevel09');
          const [count, setCount] = useState(0);
          const intervalRef = useRef(null);

          const start = () => {
          intervalRef.current = setInterval(() => {
          setCount(c => c + 1);
          }, 500);
          };

          const stop = () => {
          clearInterval(intervalRef.current);
          };

          return (
          <div>
          count => {count}
          <button onClick={start}>startbutton>

          <button onClick={stop}>stopbutton>
          div>
          );
          }

          如果需要變量,useRef 是首選的 Hook。 與局部變量不同,React 確保在每次渲染期間返回相同的引用。

          這個(gè)代碼看起來(lái)是正確的,但是有一個(gè)微妙的錯(cuò)誤。 如果 start 被多次調(diào)用,那么 setInterval 將被多次調(diào)用,從而觸發(fā)資源泄漏。

          Hooks API Reference[19]: useRef[20]

          Level 10: 判空處理

          export default function Level10() {
          console.log('renderLevel10');
          const [count, setCount] = useState(0);
          const intervalRef = useRef(null);

          const start = () => {
          if (intervalRef.current !== null) {
          return;
          }
          intervalRef.current = setInterval(() => {
          setCount(c => c + 1);
          }, 500);
          };

          const stop = () => {
          if (intervalRef.current === null) {
          return;
          }
          clearInterval(intervalRef.current);
          intervalRef.current = null;
          };

          return (
          <div>
          count => {count}
          <button onClick={start}>startbutton>

          <button onClick={stop}>stopbutton>
          div>
          );
          }

          為了避免資源泄漏,如果 interval 已經(jīng)啟動(dòng),我們只需忽略調(diào)用。 盡管調(diào)用 clearInterval (null) 不會(huì)觸發(fā)任何錯(cuò)誤,但是只釋放一次資源仍然是一個(gè)很好的實(shí)踐。

          此代碼沒有資源泄漏,實(shí)現(xiàn)正確,但可能存在性能問題。

          memoizationReact 中主要的性能優(yōu)化工具。 React.memo 進(jìn)行淺比較,如果引用相同,則跳過 render 階段。

          如果 start 函數(shù) 和 stop 函數(shù)被傳遞給一個(gè) memoized 組件,整個(gè)優(yōu)化就會(huì)失敗,因?yàn)樵诿看武秩局蠖紩?huì)返回新的引用。

          React Hooks: Memoization[21]

          Level 11: useCallback

          const intervalRef = useRef(null);

          const start = useCallback(() => {
          if (intervalRef.current !== null) {
          return;
          }
          intervalRef.current = setInterval(() => {
          setCount(c => c + 1);
          }, 500);
          }, []);

          const stop = useCallback(() => {
          if (intervalRef.current === null) {
          return;
          }

          clearInterval(intervalRef.current);
          intervalRef.current = null;
          }, []);

          return (
          <div>
          count => {count}
          <button onClick={start}>startbutton>

          <button onClick={stop}>stopbutton>
          div>
          );
          }

          為了使 React.memo 能夠正常工作,我們需要做的就是使用 useCallback 來(lái)記憶(memoize)函數(shù)。 這樣,每次渲染后都會(huì)提供相同的函數(shù)引用。

          此代碼沒有資源泄漏,實(shí)現(xiàn)正確,沒有性能問題,但代碼相當(dāng)復(fù)雜,即使對(duì)于簡(jiǎn)單的計(jì)數(shù)器也是如此。

          Hooks API Reference[22]: useCallback[23]

          Level 12: 自定義 Hook

          function useCounter(initialValue, ms) {
          const [count, setCount] = useState(initialValue);
          const intervalRef = useRef(null);

          const start = useCallback(() => {
          if (intervalRef.current !== null) {
          return;
          }
          intervalRef.current = setInterval(() => {
          setCount(c => c + 1);
          }, ms);
          }, []);

          const stop = useCallback(() => {
          if (intervalRef.current === null) {
          return;
          }
          clearInterval(intervalRef.current);
          intervalRef.current = null;
          }, []);

          const reset = useCallback(() => {
          setCount(0);
          }, []);

          return { count, start, stop, reset };
          }

          為了簡(jiǎn)化代碼,我們需要將所有復(fù)雜性封裝在 useCounter 自定義鉤子中,并暴露 api: { count,start,stop,reset }。

          export default function Level12() {
          console.log('renderLevel12');
          const { count, start, stop, reset } = useCounter(0, 500);

          return (
          <div>
          count => {count}
          <button onClick={start}>startbutton>

          <button onClick={stop}>stopbutton>
          <button onClick={reset}>resetbutton>
          div>
          );
          }

          Hooks API Reference[24]: Using a Custom Hook[25]

          React Hooks Radar

          8598fdf1c35b667d5e15909bb27967a1.webp

          ? Green

          綠色 hooks 是現(xiàn)代 React 應(yīng)用程序的主要構(gòu)件。 它們幾乎在任何地方都可以安全地使用,而不需要太多的思考

          1. useReducer
          2. useState
          3. useContext

          ? Yellow

          黃色 hooks 通過使用記憶(memoize)提供了有用的性能優(yōu)化。 管理生命周期和輸入應(yīng)該謹(jǐn)慎地進(jìn)行。

          1. useCallback
          2. useMemo

          ? Red

          紅色 hooks 與易變的世界相互作用,使用副作用。 它們是最強(qiáng)大的,應(yīng)該極其謹(jǐn)慎地使用。 自定義 hooks 被推薦用于所有重要用途的情況。

          1. useRef
          2. useEffect
          3. useLayoutEffect

          用好 React Hooks 的清單

          1. 服從Rules of Hooks 鉤子的規(guī)則[26].
          2. 不要在主渲染函數(shù)中做任何副作用
          3. 取消訂閱 / 棄置 / 銷毀所有已使用的資源
          4. Prefer 更喜歡useReducer or functional updates for 或功能更新useStateto prevent reading and writing same value in a hook. 防止在鉤子上讀寫相同的數(shù)值
          5. 不要在渲染函數(shù)中使用可變變量,而應(yīng)該使用useRef
          6. 如果你保存在useRef 的值的生命周期小于組件本身,在處理資源時(shí)不要忘記取消設(shè)置值
          7. 謹(jǐn)慎使用無(wú)限遞歸導(dǎo)致資源衰竭
          8. 在需要的時(shí)候使用 Memoize 函數(shù)和對(duì)象來(lái)提高性能
          9. 正確捕獲輸入依賴項(xiàng)(undefined=> 每一次渲染,[a, b] => 當(dāng)a or 或b改變的時(shí)候渲染, 改變,[] => 只改變一次)
          10. 對(duì)于復(fù)雜的用例可以通過自定義 Hooks 來(lái)實(shí)現(xiàn)。

          參考資料

          [1]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [2]

          useEffect: https://reactjs.org/docs/hooks-reference.html#useeffect

          [3]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [4]

          useEffect: https://reactjs.org/docs/hooks-reference.html#useeffect

          [5]

          Timing of Effects: https://reactjs.org/docs/hooks-reference.html#timing-of-effects

          [6]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [7]

          useEffect: https://reactjs.org/docs/hooks-reference.html#useeffect

          [8]

          Conditionally firing an effect: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect

          [9]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [10]

          Cleaning up an effect: https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect

          [11]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [12]

          useEffect: https://reactjs.org/docs/hooks-reference.html#useeffect

          [13]

          Conditionally firing an effect: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect

          [14]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [15]

          useState: https://reactjs.org/docs/hooks-reference.html#usestate

          [16]

          Functional updates: https://reactjs.org/docs/hooks-reference.html#functional-updates

          [17]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [18]

          Is there something like instance variables?: https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables

          [19]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [20]

          useRef: https://reactjs.org/docs/hooks-reference.html#useref

          [21]

          React Hooks: Memoization: https://medium.com/@sdolidze/react-hooks-memoization-99a9a91c8853

          [22]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [23]

          useCallback: https://reactjs.org/docs/hooks-reference.html#usecallback

          [24]

          Hooks API Reference: https://reactjs.org/docs/hooks-reference.html

          [25]

          Using a Custom Hook: https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook

          [26]

          Rules of Hooks 鉤子的規(guī)則: https://reactjs.org/docs/hooks-rules.html



          推薦閱讀




          學(xué)習(xí) React Hooks 可能會(huì)遇到的五個(gè)靈魂問題

          深入淺出 React Hooks

          React 函數(shù)式組件性能優(yōu)化指南

          瀏覽 35
          點(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>
                  天天综合天天 | 丁香六月激情 | 超碰在线碰 | 天天摸天天弄 | 大屌在线观看 |