react-refresh-webpack-plugin[1]是 React 官方提供的一个 模块热替换(HMR)插件。
10年的繁昌网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。营销型网站建设的优势是能够根据用户设备显示端的尺寸不同,自动调整繁昌建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“繁昌网站设计”,“繁昌网站推广”以来,每个客户项目都认真落实执行。
在开发环境编辑代码时,react-refresh 可以保持组件当前状态,仅仅变更编辑的部分。在umi[2]中可以通过 fastRefresh: {}快速开启该功能。
这张 gif 动图展示的是使用 react-refresh 特性的开发体验,可以看出,修改组件代码后,已经填写的用户名和密码保持不变,仅仅只有编辑的部分变更了。
对于 Class 类组件,react-refresh 会一律重新刷新(remount),已有的 state 会被重置。而对于函数组件,react-refresh 则会保留已有的 state。所以 react-refresh 对函数类组件体验会更好。本篇文章主要讲解 React Hooks 在 react-refresh 模式下的怪异行为,现在我来看下 react-refresh 对函数组件的工作机制。
在热更新时为了保持状态,useState 和 useRef 的值不会更新。
在热更新时,为了解决某些问题[3],useEffect、useCallback、useMemo 等会重新执行。
如上图所示,在文本修改之后,state保持不变,useEffect被重新执行了。
在上述工作机制下,会带来很多问题,接下来我会举几个具体的例子。
- import React, { useEffect, useState } from 'react';
- export default () => {
- const [count, setState] = useState(0);
- useEffect(() => {
- setState(s => s + 1);
- }, []);
- return (
- {count}
- )
- }
上面的代码很简单,在正常模式下,count值最大为 1。因为 useEffect 只会在初始化的时候执行一次。但在 react-refresh 模式下,每次热更新的时候,state 不变,但 useEffect 重新执行,就会导致 count 的值一直在递增。
如上图所示,count 随着每一次热更新在递增。
如果你使用了ahooks[4]或者react-use[5]的 useUpdateEffect,在热更新模式下也会有不符合预期的行为。
- import React, { useEffect } from 'react';
- import useUpdateEffect from './useUpdateEffect';
- export default () => {
- useEffect(() => {
- console.log('执行了 useEffect');
- }, []);
- useUpdateEffect(() => {
- console.log('执行了 useUpdateEffect');
- }, []);
- return (
- hello world
- )
- }
useUpdateEffect 与 useEffect相比,它会忽略第一次执行,只有在 deps 变化时才会执行。以上代码的在正常模式下,useUpdateEffect 是永远不会执行的,因为 deps 是空数组,永远不会变化。但在 react-refresh 模式下,热更新时,useUpdateEffect 和 useEffect 同时执行了。
造成这个问题的原因,就是 useUpdateEffect 用 ref 来记录了当前是不是第一次执行,见下面的代码。
- import { useEffect, useRef } from 'react';
- const useUpdateEffect: typeof useEffect = (effect, deps) => {
- const isMounted = useRef(false);
- useEffect(() => {
- if (!isMounted.current) {
- isMounted.current = true;
- } else {
- return effect();
- }
- }, deps);
- };
- export default useUpdateEffect;
上面代码的关键在 isMounted
初始化时,useEffect 执行,标记 isMounted 为 true
热更新后,useEffect 重新执行了,此时 isMounted 为 true,就往下执行了
最初发现这个问题,是 ahooks 的 useRequest 在热更新后,loading 会一直为 true。经过分析,原因就是使用 isUnmount ref 来标记组件是否卸载。
- import React, { useEffect, useState } from 'react';
- function getUsername() {
- console.log('请求了')
- return new Promise(resolve => {
- setTimeout(() => {
- resolve('test');
- }, 1000);
- });
- }
- export default function IndexPage() {
- const isUnmount = React.useRef(false);
- const [loading, setLoading] = useState(true);
- useEffect(() => {
- setLoading(true);
- getUsername().then(() => {
- if (isUnmount.current === false) {
- setLoading(false);
- }
- });
- return () => {
- isUnmount.current = true;
- }
- }, []);
- return loading ?
loading:hello world;- }
如上代码所示,在热更新时,isUnmount 变为了true,导致二次执行时,代码以为组件已经卸载了,不再响应异步操作。
第一个解决方案是从代码层面解决,也就是要求我们在写代码的时候,时时能想起来 react-refresh 模式下的怪异行为。比如 useUpdateEffect 我们就可以在初始化或者热替换时,将 isMounted ref 初始化掉。如下:
- import { useEffect, useRef } from 'react';
- const useUpdateEffect: typeof useEffect = (effect, deps) => {
- const isMounted = useRef(false);
- + useEffect(() => {
- + isMounted.current = false;
- + }, []);
- useEffect(() => {
- if (!isMounted.current) {
- isMounted.current = true;
- } else {
- return effect();
- }
- }, deps);
- };
- export default useUpdateEffect;
这个方案对上面的问题二和三都是有效的。
根据官方文档[6],我们可以通过在文件中添加以下注释来解决这个问题。
- /* @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。内容未经允许不得转载,或转载时需注明来源: 创新互联