React项目运行环境与执行顺序及动态路由等使用注意点
目录
问题起因:
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样式文件等
文件执行顺序
Node.js启动开发服务器(npm start)
加载构建配置(webpack, babel)
编译源代码(JSX转换、TypeScript编译等)
浏览器加载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>
加载并执行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);
渲染根组件(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;
初始化路由(React Router配置)
渲染匹配的路由组件
组件生命周期执行
对于上面我遇到的问题中:
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 的功能:
安装数据获取插件:
npm install umi-plugin-static-props
配置
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() } };}} };
- 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)
上面内容经供参考,包含个人看法,不是很准确,有点模糊,可能存在问题,有问题请指正,感谢!
----------到底啦----------