自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React Hooks 在 React-refresh 模塊熱替換(HMR)下的異常行為

開發(fā) 前端
本篇文章主要講解 React Hooks 在 react-refresh 模式下的怪異行為,現(xiàn)在我來看下 react-refresh 對(duì)函數(shù)組件的工作機(jī)制。

[[400802]]

 什么是 react-refresh

react-refresh-webpack-plugin[1] 是 React 官方提供的一個(gè) 模塊熱替換(HMR)插件。

  • A Webpack plugin to enable "Fast Refresh" (also previously known as Hot Reloading) for React components.

在開發(fā)環(huán)境編輯代碼時(shí),react-refresh 可以保持組件當(dāng)前狀態(tài),僅僅變更編輯的部分。在 umi[2] 中可以通過 fastRefresh: {}快速開啟該功能。

圖片

 

這張 gif 動(dòng)圖展示的是使用 react-refresh 特性的開發(fā)體驗(yàn),可以看出,修改組件代碼后,已經(jīng)填寫的用戶名和密碼保持不變,僅僅只有編輯的部分變更了。

react-refresh 的簡單原理

對(duì)于 Class 類組件,react-refresh 會(huì)一律重新刷新(remount),已有的 state 會(huì)被重置。而對(duì)于函數(shù)組件,react-refresh 則會(huì)保留已有的 state。所以 react-refresh 對(duì)函數(shù)類組件體驗(yàn)會(huì)更好。本篇文章主要講解 React Hooks 在 react-refresh 模式下的怪異行為,現(xiàn)在我來看下 react-refresh 對(duì)函數(shù)組件的工作機(jī)制。

在熱更新時(shí)為了保持狀態(tài),useState 和 useRef 的值不會(huì)更新。

在熱更新時(shí),為了解決某些問題[3],useEffect、useCallback、useMemo 等會(huì)重新執(zhí)行。

  • When we update the code, we need to "clean up" the effects that hold onto past values (e.g. passed functions), and "setup" the new ones with updated values. Otherwise, the values used by your effect would be stale and "disagree" with value used in your rendering, which makes Fast Refresh much less useful and hurts the ability to have it work with chains of custom Hooks.
圖片

如上圖所示,在文本修改之后,state保持不變,useEffect被重新執(zhí)行了。

react-refresh 工作機(jī)制導(dǎo)致的問題

在上述工作機(jī)制下,會(huì)帶來很多問題,接下來我會(huì)舉幾個(gè)具體的例子。

第一個(gè)問題

  1. import React, { useEffect, useState } from 'react'
  2.  
  3. export default () => { 
  4.   const [count, setState] = useState(0); 
  5.  
  6.   useEffect(() => { 
  7.     setState(s => s + 1); 
  8.   }, []); 
  9.  
  10.   return ( 
  11.     <div> 
  12.       {count
  13.     </div> 
  14.   ) 

 上面的代碼很簡單,在正常模式下,count值最大為 1。因?yàn)?useEffect 只會(huì)在初始化的時(shí)候執(zhí)行一次。但在 react-refresh 模式下,每次熱更新的時(shí)候,state 不變,但 useEffect 重新執(zhí)行,就會(huì)導(dǎo)致 count 的值一直在遞增。

圖片

如上圖所示,count 隨著每一次熱更新在遞增。

第二個(gè)問題

如果你使用了 ahooks[4] 或者 react-use[5] 的 useUpdateEffect,在熱更新模式下也會(huì)有不符合預(yù)期的行為。

  1. import React, { useEffect } from 'react'
  2. import useUpdateEffect from './useUpdateEffect'
  3.  
  4. export default () => { 
  5.  
  6.   useEffect(() => { 
  7.     console.log('執(zhí)行了 useEffect'); 
  8.   }, []); 
  9.  
  10.   useUpdateEffect(() => { 
  11.     console.log('執(zhí)行了 useUpdateEffect'); 
  12.   }, []); 
  13.  
  14.   return ( 
  15.     <div> 
  16.       hello world 
  17.     </div> 
  18.   ) 

 useUpdateEffect 與 useEffect相比,它會(huì)忽略第一次執(zhí)行,只有在 deps 變化時(shí)才會(huì)執(zhí)行。以上代碼的在正常模式下,useUpdateEffect 是永遠(yuǎn)不會(huì)執(zhí)行的,因?yàn)?deps 是空數(shù)組,永遠(yuǎn)不會(huì)變化。但在 react-refresh 模式下,熱更新時(shí),useUpdateEffect 和 useEffect 同時(shí)執(zhí)行了。

圖片

造成這個(gè)問題的原因,就是 useUpdateEffect 用 ref 來記錄了當(dāng)前是不是第一次執(zhí)行,見下面的代碼。

  1. import { useEffect, useRef } from 'react'
  2.  
  3. const useUpdateEffect: typeof useEffect = (effect, deps) => { 
  4.   const isMounted = useRef(false); 
  5.  
  6.   useEffect(() => { 
  7.     if (!isMounted.current) { 
  8.       isMounted.current = true
  9.     } else { 
  10.       return effect(); 
  11.     } 
  12.   }, deps); 
  13. }; 
  14.  
  15. export default useUpdateEffect; 

上面代碼的關(guān)鍵在 isMounted

初始化時(shí),useEffect 執(zhí)行,標(biāo)記 isMounted 為 true

熱更新后,useEffect 重新執(zhí)行了,此時(shí) isMounted 為 true,就往下執(zhí)行了

第三個(gè)問題

最初發(fā)現(xiàn)這個(gè)問題,是 ahooks 的 useRequest 在熱更新后,loading 會(huì)一直為 true。經(jīng)過分析,原因就是使用 isUnmount ref 來標(biāo)記組件是否卸載。

  1. import React, { useEffect, useState } from 'react'
  2.  
  3. function getUsername() { 
  4.   console.log('請(qǐng)求了'
  5.   return new Promise(resolve => { 
  6.     setTimeout(() => { 
  7.       resolve('test'); 
  8.     }, 1000); 
  9.   }); 
  10.  
  11. export default function IndexPage() { 
  12.  
  13.   const isUnmount = React.useRef(false); 
  14.   const [loading, setLoading] = useState(true); 
  15.  
  16.   useEffect(() => { 
  17.     setLoading(true); 
  18.     getUsername().then(() => { 
  19.       if (isUnmount.current === false) { 
  20.         setLoading(false); 
  21.       } 
  22.     }); 
  23.     return () => { 
  24.       isUnmount.current = true
  25.     } 
  26.   }, []); 
  27.  
  28.   return loading ? <div>loading</div> : <div>hello world</div>; 

 如上代碼所示,在熱更新時(shí),isUnmount 變?yōu)榱藅rue,導(dǎo)致二次執(zhí)行時(shí),代碼以為組件已經(jīng)卸載了,不再響應(yīng)異步操作。

如何解決這些問題

方案一

第一個(gè)解決方案是從代碼層面解決,也就是要求我們?cè)趯懘a的時(shí)候,時(shí)時(shí)能想起來 react-refresh 模式下的怪異行為。比如 useUpdateEffect 我們就可以在初始化或者熱替換時(shí),將 isMounted ref 初始化掉。如下:

  1. import { useEffect, useRef } from 'react'
  2.  
  3. const useUpdateEffect: typeof useEffect = (effect, deps) => { 
  4.   const isMounted = useRef(false); 
  5.  
  6. +  useEffect(() => { 
  7. +   isMounted.current = false
  8. +  }, []); 
  9.    
  10.   useEffect(() => { 
  11.     if (!isMounted.current) { 
  12.       isMounted.current = true
  13.     } else { 
  14.       return effect(); 
  15.     } 
  16.   }, deps); 
  17. }; 
  18.  
  19. export default useUpdateEffect; 

這個(gè)方案對(duì)上面的問題二和三都是有效的。

方案二

根據(jù)官方文檔[6],我們可以通過在文件中添加以下注釋來解決這個(gè)問題。

  1. /* @refresh reset */ 

添加這個(gè)問題后,每次熱更新,都會(huì) remount,也就是組件重新執(zhí)行。useState 和 useRef 也會(huì)重置掉,也就不會(huì)出現(xiàn)上面的問題了。

官方態(tài)度

本來 React Hooks 已經(jīng)有蠻多潛規(guī)則了,在使用 react-refresh 時(shí),還有潛規(guī)則要注意。但官方回復(fù)說這是預(yù)期行為,見該 issue[7]。

  • Effects are not exactly "mount"/"unmount" — they're more like "show"/"hide".

不管你暈沒暈,反正我是暈了,╮(╯▽╰)╭。

參考資料

[1]react-refresh-webpack-plugin:

https://github.com/pmmmwh/react-refresh-webpack-plugin

[2]umi:

https://umijs.org/zh-CN/docs/fast-refresh

[3]為了解決某些問題:

https://github.com/facebook/react/issues/21019#issuecomment-800650091

[4]ahooks:

https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useUpdateEffect/index.ts

[5]react-use:

https://github.com/streamich/react-use/blob/master/docs/useUpdateEffect.md

[6]官方文檔:

https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/API.md#reset

[7]issue:

https://github.com/facebook/react/issues/21019

 

責(zé)任編輯:姜華 來源: 前端技術(shù)磚家
相關(guān)推薦

2019-08-20 15:16:26

Reacthooks前端

2022-08-21 09:41:42

ReactVue3前端

2023-11-06 08:00:00

ReactJavaScript開發(fā)

2021-03-18 08:00:55

組件Hooks React

2020-10-28 09:12:48

React架構(gòu)Hooks

2022-03-31 17:54:29

ReactHooks前端

2021-05-18 08:21:38

React HooksReact前端

2020-09-19 17:46:20

React Hooks開發(fā)函數(shù)

2019-03-13 10:10:26

React組件前端

2021-02-02 11:02:20

React任務(wù)饑餓行為優(yōu)先級(jí)任務(wù)

2022-04-16 20:10:00

React Hookfiber框架

2021-09-26 09:40:25

React代碼前端

2022-07-18 09:01:58

React函數(shù)組件Hooks

2023-02-02 08:41:14

React團(tuán)隊(duì)Vite

2021-05-11 08:48:23

React Hooks前端

2023-05-08 07:52:29

JSXReactHooks

2022-02-10 19:15:18

React監(jiān)聽系統(tǒng)模式

2020-08-10 06:31:01

React Hooks前端開發(fā)

2022-03-16 22:24:50

ReactstateHooks

2021-11-05 10:36:19

性能優(yōu)化實(shí)踐
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)