面试必备 | React项目的一些优化方案(持续更新......)
写在前面
本专栏分享一些面试时可能遇到的React相关的问题。包括性能优化,底层原理等知识。
我会用是什么、为什么、怎么做的方式为大家讲解,便于大家逻辑清晰的知道我在讲什么。
让我们开始
一、下载依赖时的 -D与-S如何选择
1.下面我言简意赅的介绍一下 -D与-S
-
-S
(--save的简写) 或--save
:将依赖添加到dependencies
(生产环境依赖) -
-D
(--save-dev的简写) 或--save-dev
:将依赖添加到devDependencies
(开发环境依赖)
如下图
2.那么什么是生产环境、什么是开发环境?
-
开发环境:程序员写代码、调试的阶段,需要各种辅助工具 (如测试库、构建工具等),这些依赖用
-D
安装。 -
生产环境:项目实际运行时的阶段 (比如用户访问的网站/APP),只需要核心运行依赖(如React/Vue等),这些用
-S
安装。
3.那么我们为什么要区分-S和-D(开发环境与生产环境)呢?有什么实际的意义?
我总结三个实际意义,不过多解释,相信大家可以自行理解
- 减少生产环境体积:避免将测试工具、构建脚本等无用代码打包进线上产品,加快用户加载速度;
- 避免安全隐患:开发工具(如调试模块)可能包含漏洞,不打包到生产环境能降低攻击风险;
- 清晰依赖管理:团队能快速区分「项目运行需要什么」和「开发需要什么」,便于维护。
4.进一步,我们讨论何时-D何时-S(用图片上的例子分析)
这里我挑几个有代表性的依赖来讲解
①-S
(生产依赖,项目运行必需)
(1)react
& react-dom
项目运行时核心库,用户界面渲染直接依赖。
(2)axios
网络请求工具,线上环境需调用API。
(3)react-router-dom
路由管理,直接影响用户访问的页面跳转逻辑。
判断标准:这些库会被打包到最终产品代码中,用户访问网站时实际加载。
②-D
(开发依赖,仅开发阶段需要)
(1)eslint
& @eslint/js
代码规范检查工具,只在开发时提示错误,不影响运行。
(2)@types/react
TypeScript类型定义,编译后会被移除,不参与线上运行。
(3)vite
& @vitejs/plugin-react
构建工具,仅用于开发调试和打包,成品中不存在。
判断标准:这些工具只在写代码、测试或构建时使用,不会出现在用户浏览器中。
如果看到这里还是不理解-D -S,也没有关系,在之后的项目打包阶段的讲解,大家会明白
二、代码风格(React组件风格)
良好的代码风格可以让面试官眼前一亮,我们每个人都应该固定下来自己的代码风格,下面推荐一种良好的React组件风格
1.在大家的views或者pages(或者其他名字)一般放置着页面组件,那么我们写页面组件风格应该怎样呢?
请看下图
一般来讲,我们需要每一个页面组件一个文件夹,文件夹中应有相应的index.jsx\index.css或者style.moduie.less,如果有页面组件有子组件的话,也可以在页面文件夹里面再建一个子组件的文件夹,文件夹里面的内容相似,值得注意的是组件文件夹的名字要首字母大写
请看下图
2.那么,这样的风格写组件有什么好处呢?
(1)首先,我们在引入组件的时候可以如下引用
我们并 不用刻意的去引用 比如说Index中的index.jsx和index.css
(2)我们在写代码的时候也不用刻意的去将About组件文件夹下的方法写成about.jsx,这样的结构更整洁,减少决策成本
(3)我们的组件文件夹首字母大写,可以和其他非组件文件夹做区分,方便开发和维护
三、循环中的key
在React 的jsx语法中,我们经常可以看到key={...}的身影,比如
<Router>
<Routes>
{routes.map(route=><Route key={route.path} path={route.path} element={<route.component/>}/>)}
</Routes>
<Button theme='primary'>按钮</Button>
</Router>
那么为什么要有这个key呢?
在 React 中,key
是动态列表渲染的关键优化手段,它的核心作用可以总结为三点:
-
精准更新
React 通过key
识别列表项的唯一性,在数据变化时只更新变动的部分,而非重新渲染整个列表。 -
避免渲染错乱
如果省略key
,React 默认使用数组索引(index)作为标识。当列表顺序变化时,可能导致组件状态错乱(如删除第一项后,第二项错误继承第一项的状态)。 -
性能提升
稳定的key
(如路由路径route.path
)能让 React 复用已有 DOM 节点,减少不必要的销毁和重建。
万一面试官问,“你如何理解key的底层实现?” 不要慌,我十分贴心的总结了一套话术,如下
"在React中,`key`是用于优化虚拟DOM对比(Diff算法)的特殊属性,它帮助React更高效地识别列表元素的变化。
从底层实现来看,React通过虚拟DOM来最小化真实DOM操作。当组件状态变化时,React会对比新旧虚拟DOM树,这个过程称为reconciliation(协调)。对于列表渲染,如果没有key,React会默认使用数组索引(index)作为对比依据,这在列表顺序变化时会导致性能问题和状态错乱。
具体来说,key的作用体现在React Fiber架构的reconcileChildren阶段:
1. React会为每个列表项创建Fiber节点,并记录其key
2. 在Diff过程中,React会优先匹配相同key的节点
3. 如果key匹配且组件类型相同,React会复用现有Fiber节点(仅更新props)
4. 如果key不匹配,React会销毁旧Fiber并创建新Fiber
在实际项目中,良好的key选择(如数据库ID、路由path等唯一标识)能带来两大优势:
1. 性能优化:减少不必要的DOM操作
2. 状态保持:避免列表顺序变化导致的组件状态错乱
相反,如果使用index作为key,在列表中间插入或删除项时,会导致后续所有项的key变化,引发不必要的重新渲染。而使用随机数作为key则会使Diff算法完全失效,因为每次渲染key都不同。
从源码层面看,React内部通过map结构存储key与Fiber节点的对应关系,这使得key的查找和匹配非常高效。这也是为什么稳定的key能显著提升列表渲染性能。"
四、项目打包时的优化
项目的打包可是大有讲究,我先来讲解一下react项目的打包
下图是我的项目目录
下图打包后的目录
有些离谱吗,为什么我的项目那么多的目录文件,打包过后就三个文件(js、css、html),我的依赖关系呢,我辛辛苦苦写的代码呢?我们可以随便打开一个文件看一看,如下图
有点意思,对吧? 下面我拿几个最关键的点来讲解
1.为何编译后的代码挤在了一起,甚至空格都没有?
主要是为了 减少文件体积 和 提升加载性能:通过移除所有非必要字符(如空格、注释)、缩短变量名(如 functionA
→ a
),并启用 Gzip 压缩,最终使浏览器能更快下载和解析代码,同时节省服务器带宽成本。
空格、换行、注释等只是给程序员看的,浏览器并不需要
2.为何我们写的.jsx等其他文件都没了,打包后的文件只有html、css、js文件
打包工具(如 Webpack/Vite)会将所有 .jsx
、.css
、图片等资源 编译合并 为浏览器可直接运行的 .js
、.css
和 index.html
,目的是 减少HTTP请求次数 并 优化加载性能(如合并多个组件为单一JS文件、内联小资源),同时隐藏源码细节,最终输出轻量、安全的静态文件供生产环境使用
可以优化性能,一个js文件可以减少http的并发数
3.js就这一个文件,如何处理的我们原先项目的依赖关系?
打包工具(如 Webpack)通过 依赖分析 将项目中所有模块(包括第三方库和本地组件)的依赖关系 递归解析,最终按照正确的执行顺序合并到一个或多个 JS 文件中,同时通过 作用域隔离 和 模块标识符映射 来确保代码间的依赖关系在打包后依然能正确执行,既保持了模块化逻辑又优化了运行性能
比如一个最简单的例子,被依赖的放在上面,依赖的放在下面
那么回归正题,我们如何去优化项目的打包?我们把注意力放在打包的“大小”上
现在,随便打开一个之前写过的项目,里面有没有我们不需要的依赖被import引入了?这便是我们要优化的点
下面举个例子,我打包一个没有优化的项目
下面是打优化后的效果
可以观察到index-DyW19ZCt.css
不论是文件大小还是传输大小都有大幅度缩减,那么是如何实现的呢?
我们需要一个插件vite-plugin-style-import,用于按需自动导入组件库的样式文件,避免全量引入 CSS 导致的性能浪费。
只需三步
1.下载依赖
2.配置文件 只需在vite.config.js文件中进行以下配置
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import {createStyleImportPlugin} from 'vite-plugin-style-import'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
createStyleImportPlugin({
libs: [
{
libraryName: 'zarm',
esModule: true,
resolveStyle: (name) => {
return `zarm/es/${name}/style/css`;
},
},
]
})],
})
3.修改代码
这里举个例子(zarm)
//import 'zarm/dist/zarm.css'
只需把导入的组件UI库样式文件注释就可以了,vite-plugin-style-import 自动导入css,而且会自动压缩css
五、移动端适配
移动端的适配是面试的热门话题,那么我们该如何适配移动端,这里给出rem的方案
1.什么是 rem 单位
rem(root em)是一个相对单位,它相对于根元素(即 元素)的字体大小。例如,如果 元素的字体大小设置为 16px,那么 1rem 就等于 16px;如果 元素的字体大小变为 20px,那么 1rem 就等于 20px。这种相对性使得 rem 单位在实现响应式设计时非常有用,因为我们可以通过动态改变根元素的字体大小来调整整个页面的布局。
2.为什么选择 rem 单位进行移动端响应式设计
在移动端开发中,我们通常会避免使用固定的像素单位(px),因为不同设备的屏幕尺寸和分辨率差异很大。使用 px 单位可能会导致页面在某些设备上显示过大或过小。而 rem 单位可以根据根元素的字体大小进行自适应调整,从而确保页面在不同设备上都能保持一致的布局和视觉效果。
3.设计稿与 rem 的换算关系
在实际开发中,我们通常会拿到一份设计稿,假设设计稿的宽度为 750px。为了方便换算,**我们可以将 10rem 对应设计稿的 750px,那么 1rem 就等于 75px。**也就是说,当 元素的字体大小设置为 75px 时,我们可以直接根据设计稿上的尺寸进行 rem 换算。
例如,设计稿上一个元素的宽度为 150px,那么在代码中我们可以将其宽度设置为 2rem(150px / 75px = 2rem)。
下面是代码的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用 rem 实现响应式页面</title>
<script>
// 立即执行函数
(function(){
function calc(){
// 获取设备屏幕的宽度
const w = document.documentElement.clientWidth;
console.log(w);
// 750 是设计稿的宽度
document.documentElement.style.fontSize = 75 * (w / 750) + 'px';
}
// 首次加载时计算并设置根元素字体大小
calc();
// 当窗口大小改变时重新计算并设置根元素字体大小
window.onresize = function(){
calc();
}
})();
</script>
<style>
*{
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- 使用 rem 单位设置元素的宽度和高度 -->
<div style="width: 10rem;height: 2rem;background-color: red;"></div>
<!-- 换行符为 0 -->
<div style="font-size: 0;">
<div style="width: 5rem;height: 2rem;background-color: green;display: inline-block;font-size: 20px;color: white;">111</div>
<div style="width: 5rem;height: 2rem;background-color: blue;display: inline-block">222</div>
</div>
</body>
</html>
其实这样的写也并不难,但是到这里还不能拿到office(儿化音),我们有更好的方法来处理rem——引入依赖
npm i lib-flexible
lib-flexible 是一个移动端自适应解决方案,通过动态计算根元素的 font-size
并配合 rem
单位实现页面元素的等比缩放,适配不同屏幕尺寸。——它可以自动完成之前我们的rem配置
这就够了吗?还不够!如果仅仅是这样的话,我们在设置宽度等大小的时候还得手动换算比如100px=XXrem,这还不够,还有一个插件——postcss-px2rem
npm i postcss-px2rem
它可以完成自动换算px->rem
代码如下
.index{
width:150px;
span{
color:red;
}
}
页面的渲染如下
六、跨域解决方案
这部分下一篇再写吧,玩一会儿星露谷去,嘿嘿。
结语
通过本专栏,我们从 依赖管理、代码规范、性能优化 到 移动端适配,系统梳理了React面试的核心知识点。掌握这些内容,能让你在开发中更高效地 减少生产包体积、提升渲染性能,并轻松实现 多端适配。
保持技术好奇心,持续探索React生态的深度。下期我们将深入 跨域解决方案等内容,帮你彻底打通前后端联调的任督二脉!