一、JS代码规范
1.关于命名
普通命名采用小驼峰式命名
let userName = 'jack'
命名是复数的时候需要加 s,比如说我想声明一个数组
let names = new Array()
每个常量都需命名,这样更利于别人读懂含义
// good
const COL_NUM = 10
let row = Math.ceil(num / COL_NUM)
// bad
let row = Math.ceil(num / 10)
命名需要符合语义化,如果函数命名,可以采用加上动词前缀:
- can 判断是否可执行某个动作
- has 判断是否含有某个值
- is 判断是否为某个值
- get 获取某个值
- set 设置某个值
//是否可阅读
function canRead(){
return true;
}
//获取姓名
function getName{
return this.name
}
2.关于字符串
统一使用双引号
用字符串模板而不是 ‘+’ 来拼接字符串
3.关于数组
用字面量赋值
// bad
const items = new Array()
// good
const items = []
用扩展运算符做数组浅拷贝
// bad
let arr = [1, 2, 3]
const len = arr.length
const copyArr = []
for (let i = 0; i < len; i += 1) {
copyArr[i] = arr[i]
}
// good
const copyArr = [...arr]
用 Array.from 去将一个类数组对象转成一个数组。
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }
// bad
const arr = Array.prototype.slice.call(arrLike)
// good
const arr = Array.from(arrLike)
4.关于对象
创建对象和数组推荐使用字面量
// good
let obj = {
name: 'Tom',
age: 15,
sex: '男',
}
// bad
let obj = {}
obj.name = 'Tom'
obj.age = 15
obj.sex = '男'
ES6 使用属性值缩写
const lukeSkywalker = 'Luke Skywalker'
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
}
// good
const obj = {
lukeSkywalker,
}
对象浅拷贝时,更推荐使用扩展运算符 …,而不是 Object.assign。解构赋值获取对象指定的几个属性时,推荐用 rest 运算符(…)。
5.关于函数
函数参数使用默认值替代使用条件语句进行赋值。
// good
function createMicrobrewery(name = 'Jack') {
...
}
// bad
function createMicrobrewery(name) {
const userNameName = name || 'Jack'
...
}
函数参数使用结构语法,函数参数越少越好,如果参数超过两个,要使用 ES6 的解构语法,不用考虑参数的顺序。
把默认参数赋值放在最后
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
尽量使用箭头函数
// bad
[1, 2, 3].map(function (x) {
const y = x + 1
return x * y
})
// good
[1, 2, 3].map((x) => {
const y = x + 1
return x * y
})
6.关于模块
一个入口只 import 一次
// bad
import foo from 'foo'
// … some other imports … //
import { named1, named2 } from 'foo'
// good
import foo, { named1, named2 } from 'foo'
在只有一个导出的模块里,用 export default 更好
// bad
export function foo() {}
// good
export default function foo() {
7.关于注释
推荐vscode插件:koroFileHeader
- 文件注释
/*
* @Description: xxx
* @Autor: wxy
* @Date: 2021-07-06 14:40:30
* @LastEditors: wxy
* @LastEditTime: 2021-07-14 09:44:54
*/
代码块注释
/** * @description: * @param {boolean} checked * @return {*} * @author: wxy */
行注释
// TODO ... // ! ... // FIXME ... // NOTE ...
下面是一些常用的注释标签
/** * @author 作者,方便定位 * @class(同义词:@constructor)标记类和构造函数 * @constant @const常量标记 * @description(同义词:@desc) 对内容进行描述 * @module 模块名称 * @enum 枚举类型标记 * @global 全局对象标记 * @param 函数参数标记 * @returns(同义词:@return)函数返回标记 * @this this指向标记 * @see 参考链接 * @memberof 标记模块间的从属关系 * @event 在模板中标记可以被触发的事件,与@fire配合使用 * @alias 将成员视为具有不同的名称。 * @Async 表示函数是异步的。 * @augments(同义词:@extends)指示符号从父符号继承并添加到父符号。 * @borrows 此对象使用来自另一个对象的内容。 * @callback 回调函数。 * @copyright 版权信息。 * @default (同义词: @defaultvalue) 默认值。 * @example 示例。 */
8.eslint规范
.eslintrc.js文件配置的代码:
"off" or 0 :关闭规则;
"warn" or 1 :将规则视为一个警告(不会影响退出码);
"error" or 2 :将规则视为一个错误 (退出码为1);
module.exports = {
// 开启推荐配置信息
// "extends": "eslint:recommended",
// 默认情况下,ESLint 会在所有父级目录里寻找配置文件,一直到根目录。如果你想要你所有项目都遵循一个特定的约定时,这将会很有用,但有时候会导致意想不到的结果。为了将 ESLint 限制到一个特定的项目,在你项目根目录下的 package.json 文件或者 .eslintrc.* 文件里的 eslintConfig 字段下设置 "root": true。ESLint 一旦发现配置文件中有 "root": true,它就会停止在父级目录中寻找。
"root": true,
// 脚本在执行期间访问的额外的全局变量
// 当访问未定义的变量时,no-undef 规则将发出警告。如果你想在一个文件里使用全局变量,推荐你定义这些全局变量,这样 ESLint 就不会发出警告了。你可以使用注释或在配置文件中定义全局变量。
"globals" : {
"window":true,
"document":true,
"$":true
},
// 设置插件
// "plugins": [
// 'html'
// ],
// 设置解析器选项(必须设置这个属性)
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
// "arrowFunctions": true,
// "experimentalObjectRestSpread": true,
// "classes": true,
// "modules": true,
// "defaultParams": true
}
},
// 启用的规则及各自的错误级别
"rules" : {
'indent': [1, 2], // 缩进使用两个空格的宽度
'no-console':1,// 禁止用console
'semi': [1, 'always', { omitLastInOneLineBlock: true }], // 使用分号结束语句,除了语句块独占一行
'no-redeclare':1,// 在同一个作用域中禁止多次重复定义
'no-unneeded-ternary': 1, // 禁止使用没有必要的三目运算符,?: 通常出现在返回结果是一个布尔值,和单个变量之间的比较(直接赋值或者使用||运算符)
'no-unused-vars': 2, // 禁止申明一个变量,但不使用
'no-use-before-define': 2, // 禁止在变量,函数,类申明前使用
'keyword-spacing': 1, // 关键词前后要加空格
'comma-spacing': 1, // 逗号运算符两边空格:前面不加,后面加
'no-dupe-keys':2, //对象字面量不定义重复的属性
'no-duplicate-case':2, // switch语句中不定义重复的case分支
'no-eq-null':2, // 不使用 == 或 != 操作符与null进行比较
'no-inner-declarations':2, // 嵌套代码块中禁止定义函数或使用var声明变量
'no-var':2, // 使用let或者const 代替 var
'no-this-before-super':2, // 使用 this 前请确保 super() 已调用
'no-undef':2, // 对于未声明的变量引用会导致警告
'no-unreachable':2, // return,throw,continue 和 break 后不要再跟代码
'eqeqeq':2, // 使用 === 代替 ==
'quotes': ['2', 'double'], // 除需要转义的情况外,字符串统一使用双引号
'use-isnan': 2, // 判断类型是不是NAN使用isNaN()
},
// 指定你想启用的环境
"env": {
"browser": true,
"node": true
}, //babel eslint都是babel公司出的
"parser": "babel-eslint"//配置解析es6
};
二、css
1.BEM规范
block:模块,名字单词间用 - 连接
element:元素,模块的子元素,以 __ 与 block 连接
modifier:修饰,模块的变体,定义特殊模块,以 – 与 block 连接
/* 举个例子 */ .block__element { } .block--modifier { }
2.初始化规范
各浏览器厂商的初始样式都不一样,为了消除不同浏览器对 HTML 文本呈现的差异,我们常引入一些初始化样式,如 normalize.css、reset.css 等,当对于这些样式的引入我们需要注意下面几种情况:
- 不使用 UI 框架,由零开始搭建
从零开始搭建的情况下,进行样式初始化,在项目最开始的时候就引入,不要在开发中途引入,避免不可预知的样式冲突。 - 不使用 UI 框架,但使用了部分插件
插件往往都带有自己特有的样式,如富文本插件,在开发中途使用初始化样式有可能导致样式错乱,所以不建议大范围的初始化,只需简单进行初始化
即可。
* {
padding: 0;
margin: 0;
}
复制代码
- 已使用 UI 框架
在明确知道需要使用 UI 框架的时候,不使用第三方初始化样式
,不管是在项目开始前还是进行中,因为 UI 框架一般都自带初始化,额外引入了反而会影响原有效果。
3.避免重绘重排
当发生重排的时候,浏览器需要重新计算布局位置与大小,不利于性能优化。
常见引起重绘重排属性和方法
添加或者删除可见的
DOM
元素;元素尺寸改变——边距、填充、边框、宽度和高度
内容变化,比如用户在
input
框中输入文字浏览器窗口尺寸改变——
resize
事件发生时计算
offsetWidth
和offsetHeight
属性设置
style
属性的值
减少重绘重排的方法
使用
transform
替代top
使用
visibility
替换display: none
,因为前者只会引起重绘,后者会引发回流(改变了布局)不要把节点的属性值放在一个循环里当成循环里的变量。
不要使用
table
布局,可能很小的一个小改动会造成整个table
的重新布局动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用
requestAnimationFrame
CSS 选择符从右往左匹配查找,避免节点层级过多
4.颜色
建议不使用命名色值。推荐vscode插件Color Highlight
/* good */
.button--success {
color: #90ee90;
}
/* bad */
.button--success {
color: lightgreen;
}
5.字体排版
字号
因为 Windows 的字体渲染机制,小于 12px 的文字显示效果极差、难以辨认。所以在 Windows 平台显示的中文内容,其字号应不小于 12px。
2.字重
font-weight 属性建议使用数值方式描述。
/* good */
h1 {
font-weight: 700;
}
/* bad */
h1 {
font-weight: bold;
}
3.行高
line-height 在定义文本段落时,应使用数值。将line-height 设置为数值,浏览器会基于当前元素设置的 font-size 进行再次计算。在不同字号的文本段落组合中,能达到较为舒适的行间间隔效果,避免在每个设置了 font-size 都需要设置line-height。如果line-height 用于控制垂直居中,应该设置成与容器高度一致。
/* good */
.container {
line-height: 1.5;
}
/* bad */
.container {
line-height: 15px;
}
三、图片
1.使用恰当的图片格式。
- jpg:适用于内容图片多为照片之类的。
- png:适用于而饰图片,通常更适合用无损压缩。
- gif: 基本上除了 gif 动画外不要使用。
- webP:大大减小图片的体积,但是移动端有兼容性问题。
2.使用雪碧图
雪碧图,CSS Sprites
,国内也叫 CSS 精灵,是一种 CSS 图像合成技术,主要用于小图片显示。
雪碧图的优点是把诸多小图片合成一张大图,利用backround-position
属性值来确定图片呈现的位置,这样就能减少 http 请求,到达性能优化的效果。
3.使用 iconfont
iconfont
(字体图标),即通过字体的方式展示图标,多用于渲染图标、简单图形、特殊字体等。
使用 iconfont
时,由于只需要引入对应的字体文件即可,这种方法可有效减少 HTTP 请求次数,而且一般字体体积较小,所以请求传输数据量较少。与直接引入图片不同,iconfont
可以像使用字体一样,设置大小、颜色及其他样式,且不存在失真的情况。
4.图片懒加载
图片过多时,可以使用图片懒加载,原理就是暂时不设置图片的 src
属性,而是将图片的 url
隐藏起来,比如先写在 data-src
里面,等某些事件触发的时候(比如滚动到底部,点击加载图片)再将图片真实的 url
放进 src
属性里面,从而实现图片的延迟加载。
四、React
1.项目目录结构
graph LR
A[src]-->assets[assets]
A-->components[components]
A-->config[config]
A-->hook[hook]
hook-->useLoading[useLoading.tsx]
A-->layout[layout]
A-->pages[pages]
A-->router[router]
A-->a[style]
a-->color[color.less]
a-->size[font-size.less]
a-->mixin[mixin]
mixin-->scroll-bar[scroll-bar.less]
a-->theme[theme]
theme-->dark[dark.less]
A-->utils[utils]
utils-->request[request.ts]
A-->srevices[srevices]
srevices-->user[user.ts]
A-->store[store]
A-->mock[mock]
A-->constants[constants]
constants--> routes[routes.ts]
components --> components1[components1]
components1-->tsx[components1.tsx]
components1-->less[components1.less]
config --> routerconfig[router.config.js]
assets-->icons[icons]
assets-->images[images]
assets-->font[font]
2.基础规则
- 一个文件声明一个组件:尽管可以在一个文件中声明多个 React 组件,但是最好不要这样做;推荐一个文件声明一个 React 组件,并只导出一个组件;
- 推荐使用函数组件
3.组件声明
组件名称和定义该组件的文件名称建议要保持一致;
// good
import Footer from './Footer';
//bad
import Footer from './Footer/index';
在src目录下的componets文件推荐使用推荐使用大驼峰命名,在pages中生命的文件采用小写和-
符号连接
4.JSX写法注意
当标签没有子元素的时候,始终使用自闭合的标签
// good <Component /> // bad <Component></Component>
如果标签有多行属性,关闭标签要另起一行 。
// good <Component bar="bar" baz="baz" /> //bad <Component bar="bar" baz="baz" />
3.在自闭标签之前留一个空格
// good
<Foo />
//bad
<Foo/>
<Foo />
<Foo
/>
4.当组件跨行时,要用括号包裹 JSX 标签。
// good
render() {
return (
<MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
// bad
render() {
return <MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>;
}
5.样式写法
React 中样式可以使用 style 行内样式,也可以使用 className 属性来引用外部 CSS 样式表中定义的 CSS 类,推荐使用 className 来定义样式。使用less 来替换传统的 CSS 写法,需要动态修改样式时,推荐使用classnames插件
五、Hooks 组件注意事项
首先我们需要满足 React 官方声明的 hooks 两个规则: https://reactjs.org/docs/hooks-rules.html
1.不要在循环中声明 hooks 钩子
// bad
function Form({isHaveName}) {
if (isHaveName) {
var [name, setName] = useState('Mary');
}
render <div>{name}</div>
}
// good
function Form({isHaveName}) {
const [name, setName] = useState(isHaveName ? 'Mary' : '');
render <div>{name}</div>
}
2.请在 React 函数的顶层声明 Hooks
// bad
function Form() {
const [name, setName] = useState("Mary");
useEffect(function persistForm() {
localStorage.setItem("formData", name);
});
const [surname, setSurname] = useState("Poppins");
useEffect(function updateTitle() {
document.title = name + " " + surname;
});
}
// good
function Form() {
const [name, setName] = useState("Mary");
const [surname, setSurname] = useState("Poppins");
useEffect(function persistForm() {
localStorage.setItem("formData", name);
});
useEffect(function updateTitle() {
document.title = name + " " + surname;
});
}
3.使用 useCallback 和useMemo减少重绘
如果是容易被频繁渲染的组件, 如列表中的组件, 请务必编写 useCallback, 并且限定激发更新的对象, 以减少重绘
// bad
import React from "react";
function RenderItem({ title }) {
return <div>{title}</div>;
}
// good
import React, { useCallback } from "react";
function RenderItem({ title }) {
return useCallback(<div>{title}</div>, [title]);
}
4.运算量大的语句请放入 useEffect
中, 并加以条件限制
// bad
function TargetComponent({ data }) {
// 较大数据的遍历操作
const nextData = data.map(fn);
return <OtherComponent data={nextData} />;
}
// good
function TargetComponent({ data }) {
const [nextData, setNextData] = useState([]);
useEffect(()=>{
// 较大数据的遍历操作, 只有在 data 改变时才进行计算
setNextData(data.map(fn))
}, [data])
return <OtherComponent data={nextData} />
}
// best
// 将 hooks 抽离成 hooks函数
function useNextData({ data }){
const [nextData, setNextData] = useState([]);
useEffect(()=>{
setNextData(data.map(fn))
}, [data])
return [nextData, setNextData]
}
function TargetComponent({ data }) {
const [nextData, setNextData] = useNextData();
return <OtherComponent data={nextData} />
}
5.复杂的 state 逻辑, 请抽离成 hooks函数
// bad
function TargetComponent({ url, options }) {
const [data, setData] = useState([]);
useEffect(async ()=>{
cosnt data = await fetch(url, options);
setData(data)
}, [])
function handleOnUpdateData (nextOptions) {
cosnt data = await fetch(url, nextOptions);
setData(data)
}
return <OtherComponent data={data} onChange={handleOnUpdateData} />
}
// best
// 将状态逻辑抽离至 hooks 文件夹内
import useFetchData from "src/hooks/useFetchData";
function TargetComponent({ url, options }) {
// 复用 state 逻辑, 是 hooks 最重要的意义
const [data, updateData] = useFetchData(url, options);
return <OtherComponent data={data} onChange={updateData} />;
}
六、提交规范
多人协作的项目中,在提交代码这个环节,也存在一种情况:不能保证每个人对提交信息的准确描述,因此会出现提交信息紊乱、风格不一致的情况。
如果 git commit
的描述信息精准,在后期维护和 Bug 处理时会变得有据可查,项目开发周期内还可以根据规范的提交信息快速生成开发日志,从而方便我们追踪项目和把控进度。
1. type说明
值 | 描述 |
---|---|
feat | 新增一个功能 |
fix | 修复一个bug |
docs | 文档变更 |
style | 代码格式(不影响功能,例如空格、分号等格式修正) |
refactor | 代码重构 |
perf | 改善性能 |
test | 测试 |
build | 变更项目构建或外部依赖(例如scopes: webpack、gulp、npm等) |
ci | 更改持续集成软件的配置文件和package中的script命令,例如scopes: Travis、Circle等 |
chore | 变更构建流程或辅助工具 |
revert | 代码回退 |
2.scope说明
scope 用于指定本次 commit 影响的范围。scope 依据项目而定,例如在业务项目中可以依据菜单或者功能模块划分,如果是组件库开发,则可以依据组件划分。(scope 可省略)
3.subject说明
subject 是本次 commit 的简洁描述,长度约定在 50 个字符以内,通常遵循以下几个规范:
- 用动词开头,第一人称现在时表述,例如:change 代替 changed 或 changes
- 第一个字母小写
- 结尾不加句号(.)
4.body
body 是对本次 commit 的详细描述,可以分成多行。(body 可省略)
跟 subject 类似,用动词开头,body 应该说明修改的原因和更改前后的行为对比。
5.规范 commit message 的好处
- 首行就是简洁实用的关键信息,方便在 git history 中快速浏览。
- 具有更加详细的 body 和 footer,可以清晰的看出某次提交的目的和影响。
- 可以通过 type 过滤出想要查找的信息,也可以通过关键字快速查找相关提交。
- 可以直接从 commit 生成 change log。
集成 Commitizen和cz-conventional-changelog
yarn add commitizen cz-conventional-changelog D
在
package.json
中增加了config.commitizen
"config": { "commitizen": { "path": "./node_modules/cz-conventional-changelog" } }
以前我们使用的是
git commit -m "msg"
,现在改为git cz
,然后按照终端提示,逐步输入信息,就能自动生成规范的commit message
自定义配置提交说明
上面的截图可以看到,git cz
终端操作提示都是英文的,如果想改成中文的或者自定义这些配置选项,我们使用 cz-customizable 适配器。
安装cz-customizable
yarn add cz-customizable -D
修改
package.json
中的config.commitizen
字段"config": { "commitizen": { "path": "./node_modules/cz-customizable" } }
在根目录下新建
.cz-config.js
文件,配置中文module.exports = { // type 类型(定义之后,可通过上下键选择) types: [ { value: 'feat', name: 'feat: 新增功能' }, { value: 'fix', name: 'fix: 修复 bug' }, { value: 'docs', name: 'docs: 文档变更' }, { value: 'style', name: 'style: 代码格式(不影响功能,例如空格、分号等格式修正)' }, { value: 'refactor', name: 'refactor: 代码重构(不包括 bug 修复、功能新增)' }, { value: 'perf', name: 'perf: 性能优化' }, { value: 'test', name: 'test: 添加、修改测试用例' }, { value: 'build', name: 'build: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)' }, { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' }, { value: 'revert', name: 'revert: 回滚 commit' } ], // scope 类型(定义之后,可通过上下键选择) scopes: [ ['components', '组件相关'], ['hooks', 'hook 相关'], ['utils', 'utils 相关'], ['element-ui', '对 element-ui 的调整'], ['styles', '样式相关'], ['deps', '项目依赖'], ['auth', '对 auth 修改'], ['other', '其他修改'], // 如果选择 custom,后面会让你再输入一个自定义的 scope。也可以不设置此项,把后面的 allowCustomScopes 设置为 true ['custom', '以上都不是?我要自定义'] ].map(([value, description]) => { return { value, name: `${value.padEnd(30)} (${description})` } }), // 是否允许自定义填写 scope,在 scope 选择的时候,会有 empty 和 custom 可以选择。 // allowCustomScopes: true, // allowTicketNumber: false, // isTicketNumberRequired: false, // ticketNumberPrefix: 'TICKET-', // ticketNumberRegExp: '\\d{1,5}', // 针对每一个 type 去定义对应的 scopes,例如 fix /* scopeOverrides: { fix: [ { name: 'merge' }, { name: 'style' }, { name: 'e2eTest' }, { name: 'unitTest' } ] }, */ // 交互提示信息 messages: { type: '确保本次提交遵循 Angular 规范!\n选择你要提交的类型:', scope: '\n选择一个 scope(可选):', // 选择 scope: custom 时会出下面的提示 customScope: '请输入自定义的 scope:', subject: '填写简短精炼的变更描述:\n', body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n', breaking: '列举非兼容性重大的变更(可选):\n', footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n', confirmCommit: '确认提交?' }, // 设置只有 type 选择了 feat 或 fix,才询问 breaking message allowBreakingChanges: ['feat', 'fix'], // 跳过要询问的步骤 skipQuestions: ['body', 'footer'], subjectLimit: 100, // subject 限制长度 breaklineChar: '|' // 换行符,支持 body 和 footer // footerPrefix : 'ISSUES CLOSED:' // askForBreakingChangeFirst : true, }
- 再次使用
git cz
,提示信息变为中文
- 再次使用