ReactHooks在React-refresh模块热替换(HMR)下的异常行为

 什么是 react-refresh

react-refresh-webpack-plugin[1]是 React 官方提供的一个 模块热替换(HMR)插件。

10年的繁昌网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。营销型网站建设的优势是能够根据用户设备显示端的尺寸不同,自动调整繁昌建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“繁昌网站设计”,“繁昌网站推广”以来,每个客户项目都认真落实执行。

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

在开发环境编辑代码时,react-refresh 可以保持组件当前状态,仅仅变更编辑的部分。在umi[2]中可以通过 fastRefresh: {}快速开启该功能。

这张 gif 动图展示的是使用 react-refresh 特性的开发体验,可以看出,修改组件代码后,已经填写的用户名和密码保持不变,仅仅只有编辑的部分变更了。

react-refresh 的简单原理

对于 Class 类组件,react-refresh 会一律重新刷新(remount),已有的 state 会被重置。而对于函数组件,react-refresh 则会保留已有的 state。所以 react-refresh 对函数类组件体验会更好。本篇文章主要讲解 React Hooks 在 react-refresh 模式下的怪异行为,现在我来看下 react-refresh 对函数组件的工作机制。

在热更新时为了保持状态,useState 和 useRef 的值不会更新。

在热更新时,为了解决某些问题[3],useEffect、useCallback、useMemo 等会重新执行。

  • 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被重新执行了。

react-refresh 工作机制导致的问题

在上述工作机制下,会带来很多问题,接下来我会举几个具体的例子。

第一个问题

 
 
 
 
  1. import React, { useEffect, useState } from 'react';
  2. export default () => {
  3.   const [count, setState] = useState(0);
  4.   useEffect(() => {
  5.     setState(s => s + 1);
  6.   }, []);
  7.   return (
  8.     
  9.       {count}
  10.     
  •   )
  • }
  •  上面的代码很简单,在正常模式下,count值最大为 1。因为 useEffect 只会在初始化的时候执行一次。但在 react-refresh 模式下,每次热更新的时候,state 不变,但 useEffect 重新执行,就会导致 count 的值一直在递增。

    如上图所示,count 随着每一次热更新在递增。

    第二个问题

    如果你使用了ahooks[4]或者react-use[5]的 useUpdateEffect,在热更新模式下也会有不符合预期的行为。

     
     
     
     
    1. import React, { useEffect } from 'react';
    2. import useUpdateEffect from './useUpdateEffect';
    3. export default () => {
    4.   useEffect(() => {
    5.     console.log('执行了 useEffect');
    6.   }, []);
    7.   useUpdateEffect(() => {
    8.     console.log('执行了 useUpdateEffect');
    9.   }, []);
    10.   return (
    11.     
    12.       hello world
    13.     
  •   )
  • }
  •  useUpdateEffect 与 useEffect相比,它会忽略第一次执行,只有在 deps 变化时才会执行。以上代码的在正常模式下,useUpdateEffect 是永远不会执行的,因为 deps 是空数组,永远不会变化。但在 react-refresh 模式下,热更新时,useUpdateEffect 和 useEffect 同时执行了。

    造成这个问题的原因,就是 useUpdateEffect 用 ref 来记录了当前是不是第一次执行,见下面的代码。

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

    上面代码的关键在 isMounted

    初始化时,useEffect 执行,标记 isMounted 为 true

    热更新后,useEffect 重新执行了,此时 isMounted 为 true,就往下执行了

    第三个问题

    最初发现这个问题,是 ahooks 的 useRequest 在热更新后,loading 会一直为 true。经过分析,原因就是使用 isUnmount ref 来标记组件是否卸载。

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

     如上代码所示,在热更新时,isUnmount 变为了true,导致二次执行时,代码以为组件已经卸载了,不再响应异步操作。

    如何解决这些问题

    方案一

    第一个解决方案是从代码层面解决,也就是要求我们在写代码的时候,时时能想起来 react-refresh 模式下的怪异行为。比如 useUpdateEffect 我们就可以在初始化或者热替换时,将 isMounted ref 初始化掉。如下:

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

    这个方案对上面的问题二和三都是有效的。

    方案二

    根据官方文档[6],我们可以通过在文件中添加以下注释来解决这个问题。

     
     
     
     
    1. /* @refresh reset */

    添加这个问题后,每次热更新,都会 remount,也就是组件重新执行。useState 和 useRef 也会重置掉,也就不会出现上面的问题了。

    官方态度

    本来 React Hooks 已经有蛮多潜规则了,在使用 react-refresh 时,还有潜规则要注意。但官方回复说这是预期行为,见该issue[7]

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

    参考资料

    [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

    网站名称:ReactHooks在React-refresh模块热替换(HMR)下的异常行为
    本文地址:http://www.shufengxianlan.com/qtweb/news29/52929.html

    网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

    广告

    声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

    猜你还喜欢下面的内容

    静态网站知识

    各行业网站