亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

react hooks線上bug后分析

發布時間:2022-02-24 17:36:30 來源:億速云 閱讀:198 作者:iii 欄目:開發技術

這篇“react hooks線上bug后分析”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“react hooks線上bug后分析”文章吧。

硬性要求

1. 必須完整閱讀一次 React Hooks 官方文檔

其中重點必看 hooks: useStateuseReduceruseEffectuseCallbackuseMemo

2. 工程必須引入 lint 插件,并開啟相應規則

lint 插件必開規則:

{
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

其中, react-hooks/exhaustive-deps 至少 warn,也可以是 error。建議全新的工程直接配 "error",歷史工程配 "warn"。

切記,本條是硬性條件。

如果你的工程,當前沒開啟 hooks lint rule,請不要編寫任何 hooks 代碼。如果你 CR 代碼時,發現對方前端工程,沒有開啟相應規則,并且提交了 hooks 代碼,請不要合并。該要求適應于任何一個 React 前端工程。

這兩條規則會避免我們踩坑。雖然對于 hooks 新手,這個過程可能會比較“痛苦”。不過,如果你覺得這兩個規則對你編寫代碼造成了困擾,那說明你還未完全掌握 hooks。

如果對于某些場景,確實不需要「exhaustive-deps」,可在代碼處加:// eslint-disable-next-line react-hooks/exhaustive-deps

切記只能禁本處代碼,不能偷懶把整個文件都禁了。

3. 如若有發現 hooks 相關 lint 導致的 warning,不要全局 autofix

除了 hooks 外,正常的 lint 基本不會改變代碼邏輯,只是調整編寫規范。但是 hookslint 規則不同,exhaustive-deps 的變化會導致代碼邏輯發生變化,這極容易引發線上問題,所以對于 hookswaning,請不要做全局 autofix 操作。除非保證每處邏輯都做到了充分回歸。

另外公司內部有個小姐姐補充道:eslint-plugin-react-hooks 從2.4.0版本開始,已經取消了 exhaustive-deps 的autofix。所以,請盡量升級工程的lint插件至最新版,減少出錯風險

然后建議開啟 vscode 的「autofix on save」。未來無論是什么問題,能把 error 與 warning 盡量遏制在最開始的開發階段,保證自測跟測試時就是符合規則的代碼。

常見注意點

依賴問題

依賴與閉包問題是一定要開啟exhaustive-deps 的核心原因。最常見的錯誤即:mount 時綁定事件,后續狀態更新出錯。

錯誤代碼示例:(此處用 addEventListener 做 onclick 綁定,只是為了方便說明情況)

function ErrorDemo() {
  const [count, setCount] = useState(0);
  const dom = useRef(null);
  useEffect(() => {
    dom.current.addEventListener('click', () => setCount(count + 1));
  }, []);
  return <div ref={dom}>{count}</div>;
}

這段代碼的初始想法是:每當用戶點擊 domcount 就加1。理想中的效果是一直點,一直加。但實際效果是 {count} 到「1」以后就加不上了。

我們來梳理一下, useEffect(fn, []) 代表只會在 mount 時觸發。也即是首次 render 時,fn 執行一次,綁定了點擊事件,點擊觸發 setCount(count + 1) 。乍一想,count 還是那個 count,肯定會一直加上去呀,當然現實在啪啪打臉。

狀態變更觸發頁面渲染的本質是什么?本質就是 ui = fn(props, state, context) 。props、內部狀態、上下文的變更,都會導致渲染函數(此處就是ErrorDemo)的重新執行,然后返回新的 view。

那現在問題來了, ErrorDemo 這個函數執行了多次,第一次函數內部的 count 跟后面幾次的 count 會有關系嗎?這么一想,感覺又應該沒有關系了。那為什么第二次又知道 count 是1,而不是 0 了呢?第一次的setCount 跟后面的是同一個函數嗎?這背后涉及到 hooks 的一些底層原理,也關系到了為什么 hooks 的聲明需要聲明在函數頂部,不允許在條件語句中聲明。在這里就不多講了。

結論是:每次 count 都是重新聲明的變量,指向一個全新的數據;每次的setCount 雖然是重新聲明的,但指向的是同一個引用。

回到正題,我們知道了每次 render,內部的 count 其實都是全新的一個變量。那我們綁定的點擊事件方法,也即:setCount(count + 1) ,這里的 count,其實指的一直是首次 render 時的那個 count,所以一直是 0 ,因此 setCount,一直是設置 count 為1。

那這個問題怎么解?

首先,應該遵守前面的硬性要求,必須要加 lint 規則,并開啟 autofix on save。然后就會發現,其實這個 effect 是依賴 count 的。autofix 會幫你自動補上依賴,代碼變成這樣:

useEffect(() => {
  dom.current.addEventListener('click', () => setCount(count + 1));
}, [count]);

那這樣肯定就不對了,相當于每次 count 變化,都會重新綁定一次事件。所以對于事件的綁定,或者類似的場景,有幾種思路,我按我的常規處理優先級排列:

思路1:消除依賴

在這個場景里,很簡單,我們主要利用 setCount 的另一個用法 functional updates。這樣寫就好了:() => setCount(prevCount => ++prevCount) ,不用關心什么新的舊的、什么閉包,省心省事。

思路2:重新綁定事件

那如果我們這個事件就是要消費這個 count 怎么辦?比如這樣:

dom.current.addEventListener('click', () => {
  console.log(count);
  setCount(prevCount => ++prevCount);
});

我們不必執著于一定只在 mount 時執行一次。也可以每次重新 render 前移除事件,render 后綁定事件即可。這里利用 useEffect 的特性,具體可以自己看文檔:

useEffect(() => {
  const $dom = dom.current;
  const event = () => {
    console.log(count);
    setCount(prev => ++prev);
  };
  $dom.addEventListener('click', event);
  return () => $dom.removeEventListener('click', event);
}, [count]);

思路3

如果嫌這樣開銷大,或者編寫麻煩,也可以用 useRef其實用 useRef 也挺麻煩的,我個人不太喜歡這樣操作,但也能解決問題,代碼如下:

const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
  dom.current.addEventListener('click', () => {
    console.log(countRef.current);
    setCount(prevCount => {
      const newCount = ++prevCount;
      countRef.current = newCount;
      return newCount;
    });
  });
}, []);

useCallback 與 useMemo

這兩個 api,其實概念上還是很好理解的,一個是「緩存函數」, 一個是緩存「函數的返回值」。但我們經常會懶得用,甚至有的時候會用錯。

從上面依賴問題我們其實可以知道,hooks對「有沒有變化」這個點其實很敏感。如果一個 effect 內部使用了某數據或者方法。若我們依賴項不加上它,那很容易由于閉包問題,導致數據或方法,都不是我們理想中的那個它。如果我們加上它,很可能又會由于他們的變動,導致 effect 瘋狂的執行。真實開發的話,大家應該會經常遇到這種問題。

所以,在此建議:

  1. 在組件內部,那些會成為其他 useEffect 依賴項的方法,建議用 useCallback 包裹,或者直接編寫在引用它的useEffect中。

  2. 己所不欲勿施于人,如果你的 function 會作為 props傳遞給子組件,請一定要使用 useCallback 包裹,對于子組件來說,如果每次render都會導致你傳遞的函數發生變化,可能會對它造成非常大的困擾。同時也不利于 react 做渲染優化。

不過還有一種場景,大家很容易忽視,而且還很容易將 useCallbackuseMemo 混淆,典型場景就是:節流防抖。

舉個例子:

function BadDemo() {
  const [count, setCount] = useState(1);
  const handleClick = debounce(() => {
    setCount(c => ++c);
  }, 1000);
  return <div onClick={handleClick}>{count}</div>;
}

我們希望防止用戶連續點擊觸發多次變更,加了防抖,停止點擊1秒后才觸發 count + 1 ,這個組件在理想邏輯下是OK的。但現實是骨感的,我們的頁面組件非常多,這個 BadDemo 可能由于父級什么操作就重新 render 了。現在假使我們頁面每500毫秒會重新 render 一次,那么就是這樣:

function BadDemo() {
  const [count, setCount] = useState(1);
  const [, setRerender] = useState(false);
  const handleClick = debounce(() => {
    setCount(c => ++c);
  }, 1000);
  useEffect(() => {
    // 每500ms,組件重新render
    window.setInterval(() => {
      setRerender(r => !r);
    }, 500);
  }, []);
  return <div onClick={handleClick}>{count}</div>;
}

每次 render 導致 handleClick 其實是不同的函數,那么這個防抖自然而然就失效了。這樣的情況對于一些防重點要求特別高的場景,是有著較大的線上風險的。

那怎么辦呢?自然是想加上 useCallback :

const handleClick = useCallback(debounce(() => {
  setCount(c => ++c);
}, 1000), []);

現在我們發現效果滿足我們期望了,但這背后還藏著一個驚天大坑。

假如說,這個防抖的函數有一些依賴呢?比如 setCount(c => ++c); 變成了 setCount(count + 1) 。那這個函數就依賴了 count 。代碼就變成了這樣:

const handleClick = useCallback(
  debounce(() => {
    setCount(count + 1);
  }, 1000),
  []
);

大家會發現,你的 lint 規則,竟然不會要求你把 count 作為依賴項,填充到 deps 數組中去。這進而導致了最初的那個問題,只有第一次點擊會 count++。這是為什么呢?

因為傳入 useCallback 的是一段執行語句,而不是一個函數聲明。只是說它執行以后返回的新函數,我們將其作為了 useCallback 函數的入參,而這個新函數具體是個啥,其實 lint 規則也不知道。

更合理的姿勢應該是使用 useMemo :

const handleClick = useMemo(
  () => debounce(() => {
    setCount(count + 1);
  }, 1000),
  [count]
);

這樣保證每當 count 發生變化時,會返回一個新的加了防抖功能的新函數。

總而言之,對于使用高階函數的場景,建議一律使用 useMemo

有些網友提供了寶貴的反饋,我繼續補充:剛使用useMemo,依舊存在一些問題。

問題1useMemo「將來」并不「穩定」

react 的官方文檔中提到:你可以把 useMemo 作為性能優化的手段,但不要把它當成語義上的保證。將來,React 可能會選擇“遺忘”以前的一些 memoized 值,并在下次渲染時重新計算它們,比如為離屏組件釋放內存。先編寫在沒有 > useMemo 的情況下也可以執行的代碼 —— 之后再在你的代碼中添加 > useMemo,以達到優化性能的目的。也就是說,在將來的某種特殊情況下,這個防抖函數依舊會失效。當然,這種情況是發生在「將來」,且相對比較極端,出現概率較小,即使出現,也不會“短時間內連續”出現。所以對于不是 「前端防不住抖就要完蛋」的場景,風險相對較小。

問題2useMemo 并不能一勞永逸解決所有高階函數場景

在示例的場景中,防抖的邏輯是:「連續點擊后1秒,真正執行邏輯,在這過程中的重復點擊失效」。而如果業務邏輯改成了「點擊后立即發生狀態變更,再之后的1秒內重復點擊無效」,那么我們的代碼可能就變成了。

const handleClick = useMemo( 
  () => throttle(() => { setCount(count + 1); }, 1000), [count] );

然后發現又失效了。原因是點擊以后,count 立即發生了變化,然后 handleClick 又重復生成了新函數,這個節流就失效了。

所以這種場景,思路又變回了前面提到的,「消除依賴」 或 「使用ref」

當然啦,也可以選擇自己手動實現一個 debouncethrottle 。我建議可以直接使用社區的庫,比如 react-use,或者參考他們的實現自己寫兩個實現。

以上就是關于“react hooks線上bug后分析”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

拉孜县| 新乡市| 太谷县| 进贤县| 滨海县| 资讯| 红桥区| 和硕县| 甘泉县| 新密市| 浦城县| 海兴县| 腾冲县| 方正县| 鄂伦春自治旗| 宿松县| 马龙县| 张家港市| 浑源县| 尤溪县| 佛冈县| 桐乡市| 大洼县| 南通市| 信丰县| 永修县| 吴旗县| 金沙县| 昭苏县| 江口县| 延津县| 原阳县| 栾城县| 尉犁县| 安阳县| 三河市| 贵州省| 响水县| 宜兰市| 鹤峰县| 东乡族自治县|