React18 严格模式下的双重渲染之谜
文章目录
- 问题
- 分析
- 解决
- 方式一:使用状态标志
- 方式二:使用 useRef
- 方式三:移除严格模式
- 方式四:使用 useCallback
问题
这是一个 React Demo 项目 ,启动后在 Chrome 控制台里发现 控制台 被打印了 “两次”,但是,在生产环境中,这个问题通常不会出现,所以可以主要关注开发环境中的行为。
// 入口文件
import { StrictMode } from 'react';
import * as ReactDOMClient from 'react-dom/client';
import App from './App';
const root = ReactDOMClient.createRoot(document.getElementById('root'));
root.render(<StrictMode><App /></StrictMode>
);
// 组件代码
import React, { useEffect } from 'react';const App = () => {useEffect(() => {console.log('组件挂载完成!');}, []);return <>Hello world!</>;
};
分析
-
这是 React18 才新增的特性。
-
仅在开发模式(“development”)下,且使用了严格模式(“Strict Mode”)下会触发。
生产环境(“production”)模式下和原来一样,仅执行一次。 -
之所以执行两次,是为了模拟立即卸载组件和重新挂载组件。
为了帮助开发者提前发现重复挂载造成的 Bug 的代码。
同时,也是为了以后 React的新功能做铺垫。
未来会给 React 增加一个特性,允许 React 在保留状态的同时,能够做到仅仅对UI部分的添加和删除。
让开发者能够提前习惯和适应,做到组件的卸载和重新挂载之后, 重复执行 useEffect的时候不会影响应用正常运行。 -
每次组件渲染时,React 都会更新页面 UI,然后运行 useEffect 中的代码
-
Effect 在屏幕更新之后的 rendering 进程结束的时候执行。
解决
方式一:使用状态标志
const [isDataFetched, setIsDataFetched] = useState(false);useEffect(() => {if (!isDataFetched) {getDataList();setIsDataFetched(true);}
}, [isDataFetched]);
方式二:使用 useRef
const dataFetchedRef = useRef(false);useEffect(() => {if (dataFetchedRef.current) return;dataFetchedRef.current = true;getDataList();
}, []);
方式三:移除严格模式
方式四:使用 useCallback
将 getDataList 函数用 useCallback 包裹,可以确保它的引用稳定性。