React项目运行环境与执行顺序及动态路由等使用注意点

发布于:2025-08-17 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

问题起因:

React项目在开发和生产过程中主要涉及到了两个环境:

文件执行顺序

假如我调用接口,Umi是否会等待这些异步操作完成?

下面是流程图:

解决方案(针对 Ant Design Pro/Umi)

方案 1:运行时动态路由(推荐)

方案 2:构建时预取数据(需插件支持)

方案 3:自定义构建脚本

如果我说:顶级文件执行环境为node,这句话对吗?

里面的一些内容:

1. 基础元数据

2.脚本命令(scripts)

3. 浏览器兼容性(browserslist)

4. 依赖管理

生产依赖(dependencies)

开发依赖(devDependencies)

5. 环境约束(engines)

其他常见环境问题与解决方案

问题1:服务器端渲染(SSR)中的window问题

问题2:环境变量访问

最后,我们在使用过程中需要:


问题起因:

最近遇到了点问题,react项目的动态路由,逻辑这些都写好了,我把后台的数据复制到本地模拟动态路由是可以的,但是呢,当调用接口后数据能够拿到,尴尬的是动态路由渲染不出来,实际上是拿不到,也就是说,执行顺序的问题导致的,也就是环境问题。

下面是我的文件的位置:

注意这是文件的位置:顶级文件!

React项目在开发和生产过程中主要涉及到了两个环境:

一个是window(浏览器环境),一个是node环境

判断是否浏览器环境的话可以通过 if (typeof window !== 'undefined') {}

先node ---> 后window

node的话,主要是用于开发工具链和构建过程:

开发阶段(运行开发服务器、构建工具)和生产阶段(构建生产包)

  • 执行构建工具(Webpack、Babel)

  • 运行开发服务器

  • 处理环境变量

  • 执行测试

相关的文件比如:package.json, webpack.config.js, .env环境变量等

浏览器环境,主要是用于:

运行实际React应用(打包后的代码在浏览器中执行)

  • 渲染用户界面

  • 执行React组件

  • 处理用户交互

相关的文件比如:index.html ,src/index.js,src/App.js,路由配置文件,React组件文件,CSS/SCSS样式文件等

文件执行顺序

  1. Node.js启动开发服务器(npm start)

  2. 加载构建配置(webpack, babel)

  3. 编译源代码(JSX转换、TypeScript编译等)

  4. 浏览器加载HTML入口文件(index.html)

    浏览器加载并解析 public/index.html 文件,这是React应用的入口点。
    
    <!-- public/index.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8" />
      <title>React App</title>
    </head>
    <body>
      <!-- 根DOM容器 -->
      <div id="root"></div>
    </body>
    </html>
  5. 加载并执行JavaScript入口文件(index.js)

    览器解析到 index.js 文件后,下载并执行该脚本。
    
    // src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import './index.css';
    import App from './App';
    
    // 定位根DOM节点
    const rootElement = document.getElementById('root');
    
    // 创建React根
    const root = ReactDOM.createRoot(rootElement);
  6. 渲染根组件(App.js)

    在入口文件中调用 root.render() 方法,开始渲染根组件。
    
    // src/index.js 
    // 渲染根组件
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    
    App.js 组件被加载和初始化,开始构建组件树。
    
    // src/App.js
    import React from 'react';
    import './App.css';
    
    function App() {
      return (
        <div className="App">
          <header className="App-header">
            <!-- 应用内容 -->
          </header>
        </div>
      );
    }
    
    export default App;
  7. 初始化路由(React Router配置)

  8. 渲染匹配的路由组件

  9. 组件生命周期执行

对于上面我遇到的问题中:

1. Node.js环境(SSR)没有window对象浏览器API(如 `localStorage`)只有在浏览器环境中才可用

2. 即使没有SSR,在组件的顶层作用域直接访问 `localStorage` 也会在组件挂载前执行

3.在组件渲染的初始阶段(包括路由配置)执行时机过早

4..在客户端渲染时,只会在首次导入时执行一次,可能读取不到最新的值

node输出的路由只有本地的,没有从后台或者本地获取的:

我代码里面虽然判断了是否window环境,但是这里node就执行了,所以说下面这个动态路由直接为空数组了,也就是说,动态路由需要后台获取的或者调用api(这里调用接口是异步的,node环境使用axios调用也是一样的,数据是能够拿到的)的就不能放在顶级文件中,需要换位置,一般放在src文件中的某处

(我这里是登录后获取到的用户信息里面包含了,直接缓存到本地了)

可以在组件挂载后,使用useEffect+useState等调用浏览器的api这些

假如我调用接口,Umi是否会等待这些异步操作完成?

我关心的是在构建过程中,Umi是否会等待这些异步操作完成,然后再继续执行后续的静态路由和动态路由的导出操作?

我尝试使用过调用后台的api接口,并让等待其执行后,执行导出静态+动态路由。

但是,目前这个项目是基于(Ant Design Pro ) Umi 框架的,而不是 Next.js,比较遗憾,没啥用。

在Umi框架中,默认情况下,在构建时不会等待你在路由配置文件中进行的异步数据获取,要求是同步的,不能使用await!!!

1. 路由配置文件的作用:`config/routes.tsx`(或类似配置文件)的主要目的是定义路由结构,而不是执行数据获取。它通常是同步的。

2. 数据获取的时机:在Umi项目中,数据获取通常发生在以下环节:

   2.1 - 页面组件内:使用`useEffect`(客户端获取)或通过Umi的服务器端渲染(SSR)     能   力   (在`getInitialProps`或类似方法中)。

   2.2 - 服务端渲染(SSR):如果你启用了SSR,那么数据获取会在服务端进行,但这是在请求时(request time)而不是构建时(build time)。

3. 动态路由的生成:Umi支持动态路由(例如`/user/:id`),但动态路由的生成(即生成多个具体的路由路径)通常需要你在构建时通过插件或脚本预先确定这些路径

Umi本身不会在构建时去调用API获取动态路由的路径列表,动态路由的生成必须在构建前通过脚本准备好

下面是流程图:

解决方案(针对 Ant Design Pro/Umi)

方案 1:运行时动态路由(推荐)

在页面组件内处理异步数据,而不是在路由配置中:

// config/routes.tsx (静态定义路径)
export default [
  {
    path: '/dynamic/:id',
    component: '@/pages/DynamicPage',
  }
];

// src/pages/DynamicPage.tsx
import { useParams } from 'umi';

export default function DynamicPage() {
  const params = useParams<{ id: string }>();
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(`/api/data/${params.id}`);
      setData(await response.json());
    };
    fetchData();
  }, [params.id]);

  return data ? <div>{data.content}</div> : <Loading />;
}
方案 2:构建时预取数据(需插件支持)

使用 Umi 插件实现类似 SSG 的功能:

  1. 安装数据获取插件:

    npm install umi-plugin-static-props
  2. 配置 config/config.ts

export default {
  plugins: ['umi-plugin-static-props'],
  staticProps: {
    dynamicPaths: async () => {
      const res = await fetch('https://api.example.com/dynamic-paths');
      return res.json().map(id => ({ params: { id } }));
    },
    getProps: async ({ params }) => {
      const res = await fetch(`https://api.example.com/data/${params.id}`);
      return { props: { data: await res.json() } };
    }
  }
};
  1. 3.修改页面组件:
// src/pages/DynamicPage.tsx
export default function DynamicPage({ data }) {
  return <div>{data.content}</div>;
}

// 声明静态属性
DynamicPage.getStaticProps = async ({ params }) => {
  // 此函数在构建时由插件调用
};
方案 3:自定义构建脚本

在 package.json 中添加预构建脚本:

{
  "scripts": {
    "prebuild": "node ./scripts/fetchDynamicRoutes.js",
    "build": "umi build"
  }
}
// scripts/fetchDynamicRoutes.js
const fs = require('fs');
const fetch = require('node-fetch');

(async () => {
  // 1. 获取动态路由数据
  const routes = await fetch('https://api.xxx.com/dynamic-paths').then(r => r.json());
  
  // 2. 生成路由配置文件
  const routeConfig = `
    export default ${JSON.stringify(
      routes.map(id => ({
        path: `/dynamic/${id}`,
        component: '@/pages/DynamicPage',
        // 注入预获取数据
        data: ${JSON.stringify(await fetchData(id))} 
      })),
      null,
      2
    )};
  `;

   // 3. 写入临时路由文件
 fs.writeFileSync('./src/.temp/routes.ts', routeConfig);
})();

async function fetchData(id) {
  const res = await fetch(`https://api.xxx.com/data/${id}`);
  return res.json();
}
// config/routes.tsx
import 'src/.temp/routes'; // 导入生成的配置

对于大多数 Ant Design Pro 项目,建议采用方案 1(运行时获取)结合 客户端缓存,既能保持开发简单性,又能提供良好用户体验。

如果需要 SEO 支持,可配合方案 2 或方案 3 实现部分路由的静态化。

如果我说:顶级文件执行环境为node,这句话对吗?

 React应用的构建过程(编译、打包通常Node.js环境中进行。

// Webpack/Vite 等构建工具在 Node 环境中处理 React 文件
//  所有 import/require 语句由 Node 处理
import React from 'react';

 React应用的运行环境主要是浏览器(客户端)。

 如果使用服务端渲染(SSR),则部分代码(包括顶级文件)会在Node.js服务器环境中执行。

举例:

// 这个文件可能被 Node 和浏览器执行 (SSR 时)
export default function Page({ data }) { // Node 获取 data (getServerSideProps)
  return (
    <div>
      {/* 这部分 DOM 操作在浏览器执行 */}
      <button onClick={() => console.log('Click!')}>
        {data.title} {/* 文本由 SSR 注入 */}
      </button>
    </div>
  )
}

// 这段只在 Node 执行 (Next.js)
export async function getServerSideProps() {
  const res = await fetch('https://api.example.com/data'); // Node 环境请求
  return { props: { data: await res.json() } };
}

因此,原话“react的顶级文件执行环境为node”是片面的

正确的描述应该是:React 应用的源码处理和构建阶段在 Node 环境中运行,但运行时逻辑主要在浏览器环境执行。服务端渲染时组件代码会在 Node 环境先执行一次。

这里的话简单讲下node的这个package.json文件吧

package.json 是 Node 项目的 “配置中心”,统筹 元数据、脚本、依赖、环境约束

简单介绍下下面这个文件吧

package.json里面的一些内容:

1. 基础元数据

{
  "name": "ant-design-pro",
  "version": "6.0.0",
  "private": true,
  "description": "An out-of-box UI solution for enterprise applications"
}
  • name:项目名称,用于标识项目(若发布到 npm 仓库,名称需唯一)。
  • version:项目版本号,遵循 语义化版本规范主版本.次版本.补丁版本)。
  • private:设为 true 时,项目不会被发布到 npm 公共仓库(避免私有项目误发布)。
  • description:项目功能描述,方便开发者理解项目定位。

2.脚本命令(scripts

"scripts": { ... }
  • 用于定义 npm 脚本,通过 npm run <脚本名> 运行命令(如 npm run start 启动开发服务)。
  • 常见脚本:start(开发环境启动)、build(生产打包)、test(单元测试)等(具体内容因项目而异)。

3. 浏览器兼容性(browserslist

"browserslist": [
  "> 1%",
  "last 2 versions",
  "not ie <= 10"
]
  • 定义项目 支持的浏览器范围,影响以下工具:
    • Babel:决定哪些 ES6+ 语法需要转译为 ES5(兼容旧浏览器)。
    • Autoprefixer:决定哪些 CSS 属性需要添加浏览器前缀(如 -webkit--moz-)。
  • 规则解析:
    • > 1%:覆盖 全球市场份额超过 1% 的浏览器(数据来自 Can I Use)。
    • last 2 versions:每个浏览器的 最后两个正式版本
    • not ie <= 10排除 IE 10 及更早版本(即不支持 IE 10 以下浏览器)。

4. 依赖管理

生产依赖(dependencies
"dependencies": { ... }
 
  • 项目 运行时必须的依赖(如 React、Ant Design 组件库、业务逻辑库等),会被打包到生产环境。
开发依赖(devDependencies
"devDependencies": { ... }

 
  • 仅 开发阶段需要的依赖(如 Webpack、Babel、ESLint、测试框架等),生产环境无需安装。

5. 环境约束(engines

"engines": {
  "node": ">=12.0.0"
}

 
  • 指定项目运行所需的 Node.js 版本范围(这里要求 Node.js ≥ 12.0.0)。
  • 作用:确保团队成员 / CI 环境使用兼容的 Node 版本,避免因版本差异导致的问题。

 举例:

{
  "name": "my-react-app",
  "version": "0.1.0",
  "scripts": {
    // Node.js环境下执行的脚本
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test"
  },
  "dependencies": {
    // 运行时依赖(浏览器环境)
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    // 开发依赖(Node.js环境)
    "webpack": "^5.75.0",
    "babel-loader": "^9.1.2"
  }
}

当然,除了上面这些还有其他的一些需要注意的:

其他常见环境问题与解决方案

问题1:服务器端渲染(SSR)中的window问题

// 错误:直接访问window对象
const isMobile = window.innerWidth < 768;

// 正确:使用useEffect和useState
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
  const handleResize = () => {
    setIsMobile(window.innerWidth < 768);
  };
  window.addEventListener('resize', handleResize);
  handleResize(); // 初始调用
  return () => window.removeEventListener('resize', handleResize);
}, []);

问题2:环境变量访问

// .env文件(Node.js环境)
API_URL=https://api.example.com

// React组件中访问(浏览器环境)
// 错误:process.env是Node.js环境变量
const apiUrl = process.env.API_URL;

// 正确:使用REACT_APP_前缀
// .env文件:REACT_APP_API_URL=https://api.example.com
const apiUrl = process.env.REACT_APP_API_URL;

最后,我们在使用过程中需要:

  • 始终在useEffect中访问浏览器API(localStorage, window等)
  • 为异步操作添加加载状态
  • 使用环境变量时添加REACT_APP_前缀
  • 避免在模块顶层作用域访问浏览器API
  • 在SSR中使用动态导入(dynamic import)延迟加载浏览器相关组件
  • 使用自定义hook封装环境相关逻辑

 所有依赖浏览器环境的操作都应在组件挂载后执行(useEffect)

上面内容经供参考,包含个人看法,不是很准确,有点模糊,可能存在问题,有问题请指正,感谢!


----------到底啦----------



网站公告

今日签到

点亮在社区的每一天
去签到