前端代码评审规范
- 代码千万行,安全第一行;代码不规范,维护两行泪。
- 阅读规范前,请先阅读项目自带的README.md,配置好项目代码统一的格式检查。
一、代码规范
1. 文件规范
1.1 文件目录模块划分
1.1.1 文件创建原则
- 安放文件时遵循“以功能关联划分为主、类型关联划分为辅”的原则。
- 关联文件之间(如果不是就近当前文件夹内安放)必需在文件中注释写明文件功能的关联关系(必要时两边文件都需要写明)。
- 辅助模块(config / util)需要放在当前业务模块的最顶层,不能放在某个子功能的文件夹下。
1.1.2 文件类型模块
由于各个项目命名方式不同,本文只按照类型划分,大概分为下列几种模块,项目文件众多、文件夹模块划分不明确的情况下可以参考整理。
- service 服务端模块
- src/pages 页面模块n
- src/***ponents 组件模块
- Mobx / Redux / models(Dva.js) 数据源模块
- Styles 公共样式表
- Utils 公共方法文件夹,下设多个公共模块。
比如通用数据处理方法、业务数据处理方法、业务通用(非数据处理)方法、埋点方法、特殊hook方法、第三方服务相关方法等。 - Config 业务相关公共配置
- Project-config 工程化配置文件夹
- Dockerfile & deployment.yaml
- Files / Public 根目录下静态文件
- Static 图片/视频/html等静态文件资源
1.1.3 处理参考
- 定义模糊的文件如何安放?
比如一个配置文件既可以放在书写功能的文件夹中,又可以放在最外层的Config文件夹中。
如果是工程化配置文件,应该高于业务config,放置在公共 / 顶层文件夹内。
如果是业务配置,此时应遵循功能关联为主的原则,此文件是否只被当前功能使用? - 如果只被当前功能使用,则就近安放在功能关联文件夹的顶层文件夹内。
- 如果是公用config,则可以按照类型划分,放在外层config文件夹中,并且安放时依旧遵循“功能为主原则”,区分是否有类似功能config,是否是业务config / 配置config,安放在相同功能的文件夹中。
如果不存在此功能的文件,则可以新创建一个文件,注意命名规范,写明文件功能注释,并且在Code Review中告知同项目组开发成员,大家可以一起讨论是否需要新开文件还是安放在其他文件内。
2. 命名规范
常用的命名方式有:小驼峰(camelCase)、大驼峰(PascalCase)、短横线(-连接)、下划线(_链接)。
参考命名一部分参考后端开发的命名规范,有助于前后对接时减少转换损耗:阿里巴巴Java开发手册
2.1 业务文件夹命名
业务文件泛指页面page文件夹。
- 文件目录命名:小驼峰式
- 有复数结构时,要采用复数命名法,assets、***ponents、directives、mixins、utils、views等。
- 不能使用中文拼音命名,使用英文单词命名。
- 不允许以非英文字符开头,和以非英文字符结尾。
- 尽量不采用缩写单词,除非语义非常明确,名称可以取长名字,重点是语义清晰。
2.2 类命名
- 类组件采用大驼峰式命名。
- 基础类、抽象组件类采用Base开头命名,比如:Base.jsx,BaseButton.jsx,BasePicker.jsx等等。
- 方法类也遵循大驼峰式命名,比如通用的class方法,将会被其他class方法继承。
2.3 组件命名
组件的定义为:继承了React***ponent类的组件,或者Hook组件。
- 组件命名采用大驼峰式命名。
- 组件命名使用全名词,由两个以上的单词组成,推荐格式为【引用路径+副词+主要功能】。有利于文件夹顺序排列展示。
- 引用路径:尽量以当前组件被引用的页面相对路径开头,放置在相对路径的***ponents文件夹中。eg:Product、OrderList、OrderDetail等等。
- 副词:修饰组件功能的词汇。
eg:移动端专用组件:Mobile、通用业务组件:***mon、基础架构的全局组件:Global - 主要功能:如果是通用组件,则以主要功能开头,放置在最顶层的***ponents文件夹中,功能命名可参考Antd组件,最后的名词与功能紧密相关,推荐eg:List 、Table、Modal、Form、Upload、Action、ActionButtons、DataDisplay、DataView、Text、Item等等。
- 如果组件命名时引用路径指向明确为当前文件夹名称,可酌情省略引用路径,直接命名。
- 实在想不出怎么取英文名可以直接找GPT帮忙取一个。
- index命名仅限于当前目录下同名导出的文件,但是export default导出的组件禁止取名Index,最好以文件夹名称(以想要使用的组件名为导出名称),便于索引和引用提示。
// 比如:Select组件文件夹下,导出index.js文件为组件。
~/Select/index.js
// ❌
class Index extends ***ponent{}
export Index;
// ☑️
class Select extends ***ponent{}
or function Select(props){}
export default Select;
- 和父组件紧密耦合的子组件应该以父组件名作为前缀命名。(根据第2点规则,使用统一路径前缀也可以)
- 比如List页面下存在多个子组件,应该以List开头设置子组件名称。
***ponents
├─ List.js
├─ ListItem.js
├─ ListItemDataView.js // ❌
├─ StateAction.js // ❌
// ☑️
***ponents
├─ List.js
├─ ListItem.js
├─ ListItemDataView.js
├─ ListItemStateAction.js
- 如果***ponents中存在大量相同前缀的组件,可以考虑创建一个总前缀的文件夹,将内部组件放置其中,或者将相同前缀的文件从公共***ponents中移出到相对路径的***ponents文件夹下。避免前缀过长带来的负担。但是一般只推荐在非常大型 (如有 100+ 个组件) 的应用下才考虑这么做,因为在多级目录间找来找去,要比在单个 ***ponents 目录下滚动查找要花费更多的精力。
2.4 变量 / 常量 命名
- 一般业务上,在函数作用域中声明的变量/常量命名,采用小驼峰式。(可以与后端传参格式一致)
- 布尔类型:需要一个标识变量含义的前缀,比如has, is, can, should等
- 数组/集合等复数形式:最好以s或list等能够标识复数形式的后缀结尾,标识当前变量是复数形式,提高可读性
- 贯穿页面生存周期,始终存在的常量,使用全大写方式,中间以下划线分割,如:MAX_COUNT
- 系统变量、常量,可使用__两个下划线开头命名。
- 其他前缀规范:
- 只在当前函数作用域中暂时使用的临时变量,以命名简要清晰、方便书写为准即可,比如以_开头命名等。
- DOM对象 / JQuery对象 : $变量名。
- 全局使用的变量,为了特殊说明,可以使用G_变量名(大驼峰)方式命名。
- 或者其他项目中内部约定的命名方式。
2.5 方法命名
- 方法命名采用小驼峰式,以动词+宾语(动宾短语)的方式命名,如果宾语是组件本身,并且功能明确,可以采用on+动词的方式命名 ,(参考antd的方法参数命名),必要时添加注释区别。
动词:get、query、handle、show、change、open、search、select等等
宾语:List、Modal、Data等等。
on+动词:onChange、onSelect、onShow等等。
2.6 配置文件命名
- 配置文件命名以小驼峰方式,统一以config.js结尾。
- 配置文件在pages页面文件夹下,根据Next项目最佳实践规则,应采用.jsx后缀。
2.7 样式文件命名
- Less文件命名
- 跟随引用组件的命名,以module.less结尾。
- 同一个less被多个文件同时引用,可以不跟随引用组件命名,取统一的文件夹名称为名称或者index,但是不允许非本文件夹下的文件跨文件引用less。
- 只有在公共样式表中允许以.less结尾。
- 移动端less文件应以mobile.module.less命名,和非移动端less文件区分。
- class命名
- CSS模块,以module.less后缀命名的文件内的className,以小驼峰方式命名,下划线方式根据实际情况酌情使用。
- 全局样式表,直接.less后缀命名的文件内的className,以短横线方式命名。(参考antd)
- id命名
- 暂时不规定id的命名规范,自定义即可
- id不能以数字开头命名,数字开头命名的id在querySelector方法中会报错
querySelector method uses CSS3 selectors for querying the DOM and CSS3 doesn’t support ID selectors that start with a digit
- 避免出现相同的id命名,尽量以当前文件组件名开头。
- 全局层面id,可以g_开头命名
2.8 Cookie / LocalStorage 数据命名
- 采用下划线方式命名。
- 项目内部设置的cookie / localStorage应该采用xxx(项目自行规定)_开头。便于数据整合。
2.9 静态文件 / 媒体资源命名
- 媒体资源采用短横线命名,如:app-add.jpg。
2.10 项目常用缩写
通用缩写,列在此处供参考使用。
3. JavaScript书写
本篇在大部分情况下适用,小部分情况下可根据业务情况灵活处理。
大部分情况下Eslint自动格式化可以帮我们检索出格式错误和警告不推荐使用的方法,使我们无需关注自己的代码格式是否符合项目规则,又是否与其他人的格式相同,因此JS书写中我们可以更关注一些功能、逻辑上的规范。
3.1 常见eslint中的javascript规范
3.1.1 eslint: indent
项目缩进空格一致,本项目以2个空格为缩进。
3.1.2 eslint: no-undef
使用未声明的变量。
3.1.3 eslint: no-var
不使用var声明变量,使用const和let,避免污染全局作用域。
第三方SDK的var声明可忽略。
3.1.4 eslint: no-console
禁止console代码。
3.1.5 eslint: no-loop-func
不要在非函数代码块(if , while 等)中声明函数
3.1.6 eslint: eqeqeq
- 使用 === 和 !== 而非 == 和 !=
- 条件声明例如 if 会用 ToBoolean 这个抽象方法将表达式转成布尔值并遵循如下规则
- Objects 等于 true
- Undefined 等于 false
- Null 等于 false
- Booleans 等于 布尔值
- Numbers 在 +0, -0, 或者 NaN 的情况下等于 false, 其他情况是 true
- Strings 为 ‘’ 时等于 false, 否则是 true
3.1.7 eslint: no-const-assign
禁止对const常量重新赋值。
3.1.8 eslint: no-unreachable
禁止在return、throw、continue和break语句后出现不可达代码。
3.1.9 eslint: prefer-spread
建议使用扩展运算符(…)而不是.apply()来调用可变参数的函数。
3.1.10 eslint: prefer-arrow-callback
建议在回调函数中使用箭头函数而不是匿名函数,以避免this指向问题。
3.2 函数方法
- 参数传递:
- 最好存在默认参数。
- 在函数体内对传入的参数进行校验,确保参数的类型和有效性。
- 引用传值的参数在其他地方使用的情况下,不应直接改变参数的值。
- 方法参数多于3个以上应该采用对象方式传递,而非继续在原有基础上增加实参,有利于复用方法。
- 避免全局变量
- 禁止在函数体内声明全局变量,全局变量应该在引用数据整体的作用域中声明。
- 可以使用模块化的方式来组织代码,减少全局污染。
- 单一职责
- 函数应该专注于完成一个单一的任务,保持函数的简洁和可维护性。
- 错误处理
- 在函数内部进行适当的错误处理,包括使用try-catch块来捕获异常。详见下方的错误处理模块。
- 不在循环体中声明函数,避免反复的销毁和创建或者其他的变量问题。
- 避免回调地狱
- 当涉及异步操作时,尽量使用Promise、async/await或者其他异步编程方式,避免回调地狱,保持代码的可读性和维护性。
- 方法块之间应空行。
3.3 变量创建
- 尽量使用const / let
- 变量声明位置:警惕变量提升带来的使用问题,不应在声明之前使用变量。
- 不创建同名变量,代码中存在同名变量会导致变量覆盖和意外行为。
- 初始化赋值,声明变量时应尽量立即进行初始化,避免声明后未赋初值的情况。
- 在组件、方法内创建变量应该赋予初始值,如果没有赋予初始值,应该在使用前做非空判断。
- 初始值赋予需要正确提前判断数据类型,比如一个string类型的字段不能赋予对象初始值。否则后续使用类型方法时如果初始值类型错误可能会报错。
常见数据类型和对应初始赋值:- string:‘’,
- number:0,
- object:{},
- array:[],
- boolean:false,
3.4 非空处理
- 对后端回传的字段都需要做非空处理,不能直接信任,只要是复杂类型的数据(object / array),使用时都需要加一层非空判断。
不仅是在数据处理中,在组件使用数据中也应该如此。
❌
request().then(res=>{
res?.data?.list.forEach((d)=>{
d.detailDTO.forEach((t)=>{
t.name=t.tempName;
})
})
})
☑️ 使用?. 和 if非空判断 并且 如果有需要,判断为空后做数据初始化兜底。
request().then((res={})=>{
res?.data?.list?.forEach((d={})=>{
d?.detailDTO?.forEach((t)=>{
if(t){
t.name=t.tempName || '';
}
})
})
})
- 非空处理中替代的初始化参数一定要类型正确,因为后续组件使用时可能会使用类型方法。
- 代码书写中也要注意因为逻辑疏漏 / 场景缺失造成的非空判断的情况。
这一部分只能靠自身梳理,所以更应该注意。
- 方法处理的返回值是否被其他地方使用,方法中是否返回空 / 非空两种情况,使用时需要对两种情况处理。
- 场景缺失造成的非空判断情况更容易疏漏。
❌
function func(item){
if(!item) return;
return item.test?.slice();
}
const A = func(item);
A.forEach(...)
☑️ 实际上虽然!item的情况可能永远不会出现(但是谁也说不准呀),因此返回值A可能存在 为空 的情况。
function func(item){
if(!item) return;
return item.test?.slice();
}
const A = func(item);
A?.forEach(...)
3.5 错误处理
错误处理捕获需要统一上报至arms整合分析。
错误描述需清晰具体,数据能上报多少上报多少。
- 同步错误使用try catch处理,不强制使用,但是如果代码存在以下情况建议try catch。
- 方法中行数超过50行。
- 方法结果被引用于html render中(当render引用的方法报错,错误将直接导致页面崩溃)。
- 复杂数据处理,多项数据处理,被复用于多个页面,多个组件中。
- 异步错误使用Promise.catch捕获。
- 异步错误中的同步内容仍然可以使用try catch处理。
- 链式调用中错误被catch后,使用throw可以将错误移交下一个catch处理,但是如果不抛出,下一个then会正常执行,因此如果不在上一个链式调用中捕获和处理错误,整个链式处理逻辑都会错误。
因此最好避免长链式调用写法,如果存在此种情况:
a. 最好在每一段同步代码中间将错误捕获,这样不会走到catch。
b. 中间的catch只抛出,只有最后的catch才处理问题。
new Promise(()=>{throw Error('test')})
.catch(()=>{console.log('catch')})
.then(()=>console.log('test'))
// log: catch test
// 即使在上一个catch中做了处理,仍然会进行then
- 只有外层存在catch处理,才能Throw Error。
- 代码中常见错误可能出现的原因,需注意:
- 数据的非空判断。
- 商品 / 套餐缺货,下架等情况是否处理。
- 数据类型判断错误,使用不存在的方法。
- JSON.parse & JSON.stringify
3.6 注释规范
常见注释写法,根据需要注释的内容自行使用,具有可读性即可。
- 注释中的首行应该写明此组件 / 功能 / 方法的功能作用。
- 次行根据组件向外暴露的props书写可以使用的字段,字段类型,是否有默认值。
- 最后可以书写备注,比如此模块用到的第三方服务的文档、说明、模块中可能需要注意的特殊逻辑等。
- 如果代码的命名清晰,结构简单,易读易懂,可以省略注释,保留代码整体阅读的流畅性。
// 行注释
/**
* 块注释
* @param:列举函数所使用的参数。其中将参数类型用大括号括起来,并在其后注释参数名及描述。
* @return:类似于@param,这里用于描述返回值的,并且该方法没有名称。
*/
/**
* 块注释
* ==============
* 分割重点说明
* ==============
*/
- 组件注释中 为了方便vscode的自动查询,组件注释应该写在function / class上,而非写在文件顶端。
❌
/**
* 注释
*/
import XXX
fuction Index(){}
☑️
import XXX
/**
* 注释
*/
fuction Index(){}
4. 图片 / 视频
- 图片视频懒加载
- lazyload
- data-src配合监听器使用。
- 图片、视频需提前定义好宽高,避免页面重排重绘 & CLS指标变动。
图片
- 图片大小
- PC端图片大小尽量不超过400kb。
- 移动端图片大小尽量不超过200kb。
- 常用压缩网站:
https://squoosh.app/
TinyPNG – ***press WebP, PNG and JPEG images intelligently
https://image***pressor.***/
- 图片格式
- 图像颜色丰富而且图片文件不太大的(40KB 以下)或有半透明效果的优先考虑 PNG24 格式
- 图像颜色丰富而且文件比较大的优先考虑 JPEG 格式。
- 条件允许的,优先考虑 WebP 代替 PNG 和 JPEG 格式。
视频
- 视频大小
- 常见的前端视频压缩码率为5M(性能和表现之间比较平衡)
以此基础上参考:10s的视频大小大概为6~7M,因此页面上的视频大小最好都在10M左右。 - 压缩方式:可以自行使用ffmpeg进行压缩。
5. CSS规范
5.1 避免过于深层的嵌套
CSS选择器的性能取决于浏览器的实现方式,以及页面中的元素数量和嵌套层次。
选择器的性能通常受以下几个因素影响:
- 选择器的复杂性:嵌套层次和选择器的组合越复杂,性能越差。
- 页面中的元素数量:如果页面中的元素数量较多,选择器的性能可能受到影响。
- 浏览器的渲染引擎:不同浏览器对选择器的解析和匹配方式有所不同。
因此,正确的选择器优化策略是专注于避免过度复杂的选择器,减少嵌套层次。
5.2 CSS样式尽量少使用范围广的匹配标签,降低CSS查询的成本。
- 尽量不使用 * 通配符选择器。
- 使用标签选择器前考虑使用 > , + , ~ 缩小选择范围。
样式系统通过从最右侧的选择器开始,从右到左遍历规则的选择器。只要生成的css树仍然是正确的,样式系统就会继续向左移动,直到它匹配规则或因不匹配而中止。
这就阐明了我们应该将优化工作重点放在哪里:在右侧选择器上,也称为关键选择器。
下面是一个更昂贵的选择器示例:A.class0007 * {}。尽管这个选择器可能看起来更简单,但浏览器的匹配成本更高。因为浏览器从右到左移动,它首先检查与关键选择器“*”匹配的所有元素。这意味着浏览器必须尝试针对页面中的所有元素匹配此选择器。有兴趣的> 开发可以尝试 使用此通用选择器的测试页面的加载时间与先前的后代选择器测试页面之间的差异。
很明显,关键选择器匹配许多元素的CSS选择器可能会明显减慢网页。其他一些关键选择器可能为浏览器创建大量工作的CSS选择器示例包括:
A.class0007 div {}
#id0007 > a {}
.class0007 [href] {}
div:first-child {}
并非所有的CSS选择器都会影响性能,关键是关注 右侧 关键选择器匹配范围广泛的CSS选择器。这在Web 2.0应用程序中尤为重要,因为DOM元素、CSS规则和页面重绘的数量更高。
❌
.classA *{}
.classA div span{}
☑️
.classA > div > span{}
5.3 避免!important
滥用important不利于维护和可能造成样式错误覆盖的情况,如果可以的情况下请使用层级叠加的方式来控制css的优先级。
目前适用场景:
- 需要覆盖第三方输入的情况下使用important,如antd、富文本编辑。
5.4 复杂的CSS样式需要书写注释
大部分情况下我们更关注JS代码中的注释,但是CSS文件中也存在一些需要书写注释的地方,比如公用样式、复杂的CSS组合,LESS函数样式等,CSS中书写注释有利于划分CSS的层级组合、样式嵌套、维护复用等。
5.5 移动端样式书写
- 常见移动端自适应方案包括:路由选择、百分比、rem、媒体查询。
- 媒体查询中的移动端样式最好整合放到一起,而非分散在每个CSS样式中。有利于整体查看移动端样式情况和降低移动端样式维护时的反复翻阅文件。
5.6 注意z-index的层级
项目中的z-index书写不能太随心所欲,本文规范中以使用的公共组件antd中规定弹窗的z-index:1002为准,如果当前组件层级需要高于页面中的弹窗展示,则可以取大于1002的值,否则,应取值小于1002。
5.7 在模块化CSS文件中书写样式
上文样式文件命名中已经提及模块化CSS和公共CSS文件的区别,为了避免全局污染,书写样式应该使用.module.less结尾。
5.8 CSS引入url
- 外部的url引入应该使用双引号扩起来,不能直接使用链接,避免url中原本存在引号导致引入失败。
backgroundImage:url("xxxx")
- 小心外部引入的CSS资源下载失败产生阻塞页面渲染的风险。
5.9 样式兼容性
- 浏览器兼容
目前常见浏览器如下,使用前应该在 https://caniuse.***/ 中查询兼容性。
- Chrome
- Safari
- firefox
- 样式兼容
此处例举几个出现过的样式问题,使用前应注意
- position: fixed 不能与 transition 一起使用,出现渲染层级问题。
- IOS11低版本,safari不能使用overflow衍生属性overflow-x ,-y,会出现overflow的效果。
6. React最佳实践
6.1 多用 Function ***ponent
如果组件是纯展示型的,不需要维护 state 和生命周期,则优先使用 Function ***ponent。它有如下好处:
- 代码更简洁,一看就知道是纯展示型的,没有复杂的业务逻辑
- 更好的复用性。只要传入相同结构的 props,就能展示相同的界面,不需要考虑副作用。
- 更小的打包体积,更高的执行效率
// 一个典型的 Function ***ponent 是下面这个样子:
function MenuItem({menuId, menuText, onClick, activeId}) {
return (<div
menuId={menuId}
className={`${style} ${activeId === menuId ? active : ''}`}
onClick={onItemClick}>{menuText}
</div>);
};
6.2 多用 Pure***ponent & React.memo
如果组件需要维护 state 或使用生命周期方法,则优先使用 Pure***ponent,而不是 ***ponent。***ponent 的默认行为是不论 state 和 props 是否有变化,都触发 render。而 Pure***ponent 会先对 state 和 props 进行浅比较,不同的时候才会 render。
参考原因链接:https://zhuanlan.zhihu.***/p/94618828?utm_id=0
写在使用前:
只建议纯组件使用,非纯组件使用前需要仔细考虑:
当前组件使用到的外部props是否是复杂类型?
是复杂类型(对象)的情况下,每次props变化是否变化引用地址?
如果是对象,变化时没有地址变动,它的变化可能会被pure***ponents过滤掉,导致组件无法render。
除了外部的props,Pure***ponent还会比较内部的State,如果内部的State是引用类型,修改时没有改state的地址,也不会触发更新。
6.3 警惕外部传入的function成为重渲染刺客
外部传入的props数据中包含function参数的,应该考虑function的传入方式,否则每次组件变动、上层组件变动,都会新创建一个函数,而函数新创建又导致子组件重渲染,造成一些性能问题。
class组件中常见的几种function绑定this操作:
- 此绑定行为是在渲染时绑定,每次父组件更新会重新创建一个新的函数,同时Eslint会发出警告。
class Parent extends ***ponents{
func(){}
render(){
return <Child func={this.func.bind(this)} />
}
}
- 此绑定行为利用了箭头函数的性质,但是在每次父组件更新也会重新创建一个新的函数。
class Parent extends ***ponents{
func(){}
render(){
return <Child func={()=>this.func()} />
}
}
- class类中的函数地址不变,作为参数不会重创建,推荐此种传入方式。
class Parent extends ***ponents{
func = ()=>{}
render(){
return <Child func={this.func} />
}
}
- 函数式组件中,每次组件的刷新都会重新生成函数上下文和内部函数 / 变量,也因此将函数作为参数传入子组件时很容易导致不必要的重渲染。此时就要考虑使用useCallback将函数包装一下,只有在它本身需要变化时才更新函数。
function Parent(){
const func = useCallback(()=>{},[test])
render(){
return <Child func={func} />
}
}
6.4 Hooks
- 注意Hook的依赖项,是否及时更新函数上下文的变量信息,注意Eslint的hook提示。
- Hook中使用防抖、节流方法时应使用ahook中的方法,避免因为函数重新创建平常使用的防抖、节流方法不生效。
- 使用useMemo、useCallback性能优化。
7. Next最佳实践
7.1 dynamic动态加载组件
import dynamic from 'next/dynamic'
- 合理代码分割,有利于加快请求速度;并可以配合懒加载方案使用,使资源加载时机得到优化。
- 动态加载时可以指定是否服务端渲染,有利于首屏渲染。
7.2 Next/Image优化图片资源
next/image
可帮助我们对图片进行压缩(尺寸 or 质量),且支持图片懒加载等,可以支持服务端图片加载、预加载等机制。
- 需要固定宽高
- 会自动对图片资源进行压缩处理,对图片清晰度有要求的情况最好不使用。
7.3 next/link 预加载
import Link from 'next/link'
可以用于页面上的固定文字超链接、导航等。基于 hover 识别用户意图,当用户 hover 到 Link 标签时,对即将跳转的页面资源进行预加载,进一步防止页面卡顿。
8. 兼容性
8.1 兼容性查找网站:https://caniuse.***/
- 在兼容性测试中达到95%+以上的兼容性为佳。
- 如果低于95%兼容性,需要做不兼容的预案,考虑可以接受不兼容的情况,再进行使用。
8.2 兼容性考虑的测试环境
一般来说测试常见浏览器为:电脑端谷歌、safari,移动端谷歌、safari,测试设备以已有设备为主。
8.3 一些已经发现的兼容性问题
IOS safari需要额外测试,safari中已经被发现的一些兼容性错误:
- 复杂正则在safari中无法识别,可能会造成渲染阻塞。
- 低版本safari中overflow扩展属性overflow-y,overflow-x不生效,会被识别为整体的overflow属性。
二、安全规范
1. 注入性攻击
- 向外部暴露的文本填空框输入的字段,不能在项目里使用html方式解析,避免外部输入脚本攻击被前端解析出来之后造成XSS攻击。
- 需要的情况下,对外部输入文本框中的文字进行尖括号转码,破坏脚本注入。
2. CSXF攻击
// 懒得写了
三、性能考虑
1. 代码分割
非首屏展示的组件可以考虑做代码分割Next/dynamic,代码分割配合组件懒加载可以达到"展示页面上才加载"的效果。