为什么 React 的函数组件每次渲染执行两次

1.这是 React18 才新增的特性。

2.仅在开发模式("development")下,且使用了严格模式("Strict Mode")下会触发。 生产环境("production")模式下和原来一样,仅执行一次。

在 React 中,当你看到某些代码执行了多次(例如,console.log 输出了两次),这通常是由几个常见原因导致的:

  1. 严格模式(Strict Mode):
    在开发模式下,React 的严格模式会故意使组件重新渲染两次,以便更容易发现潜在的副作用和其他问题。你可以检查你的 index.js 或者 main.js 文件,看看是否有 <React.StrictMode> 包裹了你的应用。
  2. 双重渲染:
    React 可能会因为某些状态变化或者父组件的重新渲染导致子组件重新渲染多次。
  3. 热模块替换(Hot Module Replacement, HMR):
    在开发过程中,HMR 可能会导致组件重新加载,尤其是在使用一些开发工具和环境时。
  4. 还有一个原因是检查组件是否为纯函数

解决方法

为了检查和解决这个问题,你可以尝试以下步骤:

1. 检查是否启用了严格模式

在 index.js 或 main.js 中,如果你看到如下代码,可以注释掉 <React.StrictMode> 并观察行为变化:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
// <React.StrictMode>
<App />,
// </React.StrictMode>,
document.getElementById('root')
);
2. 添加唯一的 key 属性:(切记,很重要!)

如果你在列表中渲染元素,确保每个元素都有唯一的 key 属性。这有助于 React 更好地管理列表项。

import React, { useState, useEffect } from 'react';

const App = () => {
  const [listData, setListData] = useState([]);

  useEffect(() => {
    // 初始化加载数据
    const initialData = Array.from({ length: 5 }, (_, i) => `Item ${i + 1}`);
    setListData(initialData);
  }, []);

  return (
      <div className="content">
        {listData.map((item, index) => (
          <div key={index} className="list-item">
            {item}
          </div>
        ))}
      </div>
  );
};

export default App;
3. 使用 useEffect 的依赖项:(最容易导致。。。。。。)

检查 useEffect 是否有明确的依赖项。比如:

useEffect(() => {
 console.log('handleLoadMore');
 handleLoadMore();
}, [依赖项数组]);
  • 第一个参数是一个回调函数,其中包含副作用代码。
  • 第二个参数是依赖项数组,控制副作用的执行时机

 当依赖项数组为空时,useEffect 仅在组件首次渲染后执行一次。之后即使组件重新渲染(比如因为状态或属性变化),该副作用也不会再次执行。

useEffect(() => {
  // 依赖项变化时执行副作用
}, [dep1, dep2]);

当依赖项数组包含特定变量时,useEffect 会在组件首次渲染后执行,并在任何一个依赖项发生变化时重新执行。

useEffect(() => {
  // 每次渲染后执行副作用
});

如果没有提供依赖项数组,useEffect 会在每次组件渲染后执行(记得设置一个空数组[])。这通常会导致性能问题,除非你的副作用非常轻量且必须每次渲染后都执行。

useEffect(() => {
  const handle = setInterval(() => {
    console.log('Interval running');
  }, 1000);

  return () => {
    clearInterval(handle);
  };
}, []);

有时候需要在组件卸载或副作用重新运行之前进行清理。例如,取消订阅或清除计时器。

开发者在编写代码时应该遵循React的最佳实践,比如:

  • 使用useStateuseEffect等Hooks时,明确依赖项数组。确保正确地列出所有外部依赖,避免不必要的副作用执行。
  • 尽可能地将计算移动到渲染之外。例如,如果有重复的计算逻辑,可以考虑使用useMemouseCallback来优化。
  • 在开发过程中利用React开发者工具。这些工具能够帮助你更好地理解组件的渲染行为和性能问题。
4. 排除 HMR 的影响

确保热模块替换没有干扰到你的调试。你可以在开发模式下尝试禁用 HMR。

(1)Create React App:

在你的 .env 文件中添加:

FAST_REFRESH=false

如果没有 .env 文件,可以在启动命令前添加环境变量:

FAST_REFRESH=false npm start

2)Webpack:

Create React App 默认隐藏了 Webpack 配置,所以你需要使用 react-app-rewired 或 craco 来覆盖配置。以下是使用 craco 的示例:

npm install @craco/craco
创建一个 craco.config.js 文件,禁用 HMR:
module.exports = {
  webpack: {
    configure: (webpackConfig, { env, paths }) => {
      if (env === 'development') {
        webpackConfig.devServer = {
          ...webpackConfig.devServer,
          hot: false,
        };
      }
      return webpackConfig;
    },
  },
};

修改 package.json 的启动脚本:

"scripts": {
  "start": "craco start",
  "build": "craco build",
  "test": "craco test",
  "eject": "react-scripts eject"
}

(3)自定义的 Webpack:

如果你有自定义的 Webpack 配置,可以直接在 webpack.config.js 中禁用 HMR:

module.exports = {
  // ...其他配置
  devServer: {
    hot: false,
  },
};

(4)Vite:

如果你使用 Vite 作为构建工具,可以通过修改 Vite 配置来禁用 HMR:

export default {
  server: {
    hmr: false,
  },
};

(5)Next.js:

Next.js 使用其自己的 Webpack 配置,禁用 HMR 可能需要一些额外步骤:

自定义 next.config.js:

module.exports = {
  webpackDevMiddleware: config => {
    config.watchOptions = {
      poll: 300,
      aggregateTimeout: 300,
      ignored: /node_modules/,
    };
    return config;
  },
};
5. 调试工具

使用 Facebook 的 React DevTools 调试工具,观察组件的渲染情况和状态变化,找到导致重复渲染的具体原因。

相关问答FAQs:

1. React 的函数组件在每次渲染时会执行两次的原因是什么?

React 的函数组件在每次渲染时执行两次的原因是 React 使用了一种称为 "Double Render" 的机制。这意味着 React 在首次渲染组件时会执行一次函数,然后比较前后两次的函数执行结果,如果发现有变化,就会触发一次重新渲染来更新页面。因此,第一次执行函数用于比较前后结果是否有变化,第二次执行函数则是用于实际渲染页面。这种机制可以确保组件在更新时只进行必要的渲染,提高性能和效率。

2. React 函数组件为什么会在渲染时执行两次而不是一次?

React 函数组件在渲染时执行两次的原因是为了支持 Hooks 机制。Hooks 是 React 16.8 版本引入的新特性,它允许我们在函数组件中使用状态和其他 React 特性,以及实现更复杂的逻辑。为了使 Hooks 正常工作,React 需要执行两次渲染,第一次以收集 Hooks 的信息,第二次以执行每个 Hook 的副作用和渲染逻辑。

3. React 函数组件为什么会执行两次而不是一次呢?有什么好处?

React 函数组件执行两次渲染的好处是可以保证副作用的正确执行和避免出现意外的副作用。副作用指的是在组件渲染过程中可能产生的对外部环境的修改,比如网络请求、订阅事件等操作。通过执行两次渲染,React 可以在第一次渲染时收集副作用信息,然后在第二次渲染时执行这些副作用,确保它们按照正确的顺序和时机执行。

这种机制可以避免副作用导致的错误,并且使得组件的逻辑更加清晰和可控。方便开发者编写高质量的代码,并且提供更好的性能。所以,尽管函数组件执行两次渲染可能会带来一些性能开销,但它带来的好处和灵活性远远超过了这些开销。

React 函数组件中,有两个主要的生命周期函数: 1. `useEffect` `useEffect` 可以看作是 `componentDidMount`、`componentDidUpdate` 和 `componentWillUnmount` 这三个生命周期函数的结合。 `useEffect` 接收一个函数作为参数,这个函数会在组件挂载后执行,也会在组件更新时执行(除非你传入了第二个参数并且这个参数的值没有变化)。如果这个函数返回了一个函数,则这个返回的函数会在组件卸载时执行。 例如: ```jsx import { useEffect } from 'react'; function MyComponent(props) { useEffect(() => { console.log('组件挂载后执行'); return () => { console.log('组件卸载时执行'); }; }, [props.someProp]); useEffect(() => { console.log('组件挂载后执行'); return () => { console.log('组件卸载时执行'); }; }, []); // ... } ``` 上面的代码中,第一个 `useEffect` 会在组件挂载后执行,也会在 `props.someProp` 的值发生变化时执行清理函数;第二个 `useEffect` 会在组件挂载后执行,但不会对任何 props 进行监听,因此它只会在组件挂载时执行一次。 2. `useState` `useState` 是一个 Hook,用于在函数组件中添加状态(state)。 使用 `useState` 可以声明一个状态变量和一个更新函数,例如: ```jsx import { useState } from 'react'; function MyComponent(props) { const [count, setCount] = useState(0); return ( <div> <p>你点击了 {count} 次</p> <button onClick={() => setCount(count + 1)}>点击我</button> </div> ); } ``` 上面的代码中,我们使用 `useState` 声明了一个状态变量 `count` 和一个更新函数 `setCount`,并将 `count` 的初始值设为 0。在组件渲染时,我们可以使用 `count` 来渲染一个按钮和一段文字,当用户点击按钮时,我们可以使用 `setCount` 来更新 `count` 的值。由于 `count` 的值发生变化,因此 React 会重新渲染组件。 除了 `useEffect` 和 `useState`,还有一些其他的 Hook 可以用于函数组件的生命周期管理,例如 `useContext`、`useReducer`、`useCallback` 等等。这些 Hook 的使用方法和作用可以参考 React 文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值