代码编写规范
概述规范的代码,是项目质量的保障。本代码规范从typescript、 javascript、css、vue 等几个方面,对日常开发中的代码编写,给出了规范性的指导与建议,能有效避免代码级别的缺陷与错误;也可依据本规范,在代码审查中,对不符合规范的代码提出问题;是 Segma 前端团队的重要规范之一。优先级说明A:必要的非特殊情况下,必须遵守的规范,通常这些规范会帮你避免错误。B:强烈推荐的被大量使
概述
规范的代码,是项目质量的保障。本代码规范从typescript、 javascript、css、vue 等几个方面,对日常开发中的代码编写,给出了规范性的指导与建议,能有效避免代码级别的缺陷与错误;也可依据本规范,在代码审查中,对不符合规范的代码提出问题。
优先级说明
A:必要的
非特殊情况下,必须遵守的规范,通常这些规范会帮你避免错误。
B:强烈推荐的
被大量使用的规则,大多数都能改善代码可读性与开发体验,但即使违反,也不会导致错误。
C:谨慎使用的
使用起来有风险的规范,这些规范可能会解决一些比较复杂的问题,但同时也会带来潜在的风险,当你编写此类代码时,需要仔细思考可能存在的问题。
命名
对文件、目录的命名进行规范。
(A)项目
全部小写,多个单词以下划线分隔。
// bad
cisdiPlatform
CisdiPlatform
// good
cisdi_platform
(A)目录、文件夹
参照项目命名,有复数结构时,使用复数命名法。
(A)javascript 文件
参照项目命名。
(A)css、less、scss 文件
参照项目命名。
(A)html 文件
参照项目命名。
(A)vue 文件
使用 PascalCase 命名规则。
// bad
headerTitle.vue
header_title.vue
// good
HeaderTitle.vue
JAVASCRIPT
javascript 代码规范的详细说明。
(A)使用 let、const,不使用 var
// bad
var bar = 1;
var PI = 3.14;
// good
let bar = 1;
const PI = 3.14;
参考:
(A)变量命名
- 变量使用 camelCase 命名;
- 常量全大写,多个单词以下划线连接;
- 使用英文,不使用拼音命名;
// bad
let table_data;
// good
let tableData;
// bad
const one_day = 24 * 3600 * 1000;
const oneDay = 24 * 3600 * 1000;
// good
const ONE_DAY = 24 * 3600 * 1000;
// bad
let shijian;
// good
let time;
(A)数组、对象格式
- 对象的属性名不要加引号,必须加引号的情况除外;
- 对象只有一个属性的,可以写在一行,超过一个属性的,每个属性换行书写并有缩进;
- 数组、对象的最后一项不要逗号;
// bad
let foo = { "a": 1 };
let bar = { a: 1, b: 2 };
// good
let foo = {
a: 1,
'prop-name': 'name'
};
(A)对象、数组浅拷贝时,更推荐使用扩展运算符
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 });
delete copy.a;
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// good
const original = { a: 1, b: 2 };
// 浅拷贝
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
// rest 解构运算符
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
(A)关键字后必须有大括号
下列关键字后必须有大括号(即使代码块的内容只有一行):if, else, for, while, do, switch, try, catch, finally, with。
const condition = Math.random() > 0.5;
// bad
if (condition) return;
// good
if (condition) {
return;
}
(A)undefined 的使用
禁止直接使用 undefined 进行变量判断;使用 typeof 和字符串’undefined’对变量进行判断。
let name;
// bad
console.log(name === undefined);
// good
console.log(typeof name === 'undefined');
(A)for-in
使用 for-in 时,一定要有 hasOwnProperty 的判断。
let foo = {
a: 1,
b: 2
};
foo.__proto__.c = 3;
// bad
for (let item in foo) {
console.log(item);
}
// 1,2,3
// good
for (let item in foo) {
if (foo.hasOwnProperty(item)) {
console.log(item);
}
}
// 1,2
(A)函数参数不能超过 3 个
超过 3 个的情况使用对象的方式进行传递。
// bad
const foo = (a, b, c, d) => {
console.log(a, b, c, d);
};
// good
const foo = args => {
const { a, b, c, d } = args;
console.log(a, b, c, d);
};
(A)禁止全局变量、禁止污染全局变量
// bad
bar = 1;
window.bar = 1;
// good
const bar = 1;
(A)使用 class 代替 function
// bad
function User(name) {
this.name = name;
}
User.prototype.hello = function() {
console.log(this.name);
};
// good
class User {
constructor(name) {
this.name = name;
}
hello() {
console.log(this.name);
}
}
(A)使用 Promise 代替回调函数
// bad
require('request').get('api_url', function(err, response) {
if (err) {
console.error(err);
} else {
require('fs').writeFile('article.html', response.body, function(err) {
if (err) {
console.error(err);
} else {
console.log('File written');
}
});
}
});
// good
const request = require('request-promise').get('api_url');
request.then(response => {
console.log(response);
});
(A)注册事件与卸载事件的代码必须同时存在
// bad
mounted() {
window.addEventListener('resize', resizeHandler);
}
// good
mounted() {
window.addEventListener('resize', resizeHandler);
},
beforeDestroy() {
window.removeEventListener('resize', resizeHandler);
}
(A)避免无意义的条件判断
优先使用 ||
、 &&
运算符。
// bad
function createMicrobrewery(name) {
let breweryName;
if (name) {
breweryName = name;
} else {
breweryName = 'Hipster Brew Co.';
}
}
// good
function createMicrobrewery(name) {
let breweryName = name || 'Hipster Brew Co.';
}
(A)使用模板字符串
避免直接拼接字符串带来的代码可读性降低、易出现 bug 等问题。
const date = '2011-01-01';
const time = '12:00';
// bad
const fullTime = date + ' ' + time;
// good
const fullTime = `${date} ${time}`;
(A)在最后的审查中,不得出现无效的、过时的代码、注释或文件
所有对代码的修改都应该可以通过 git 来追踪,因此对于无效、过时的代码、注释等没有必要保存在最新的代码库中。
(A)用 === 和 !== 而不是 == 和 !=
(B)变量命名风格
- 避免字母+数字的形式;
- 避免简写;
- 能准确表达变量作用;
- 对功能相似的变量使用统一的命名风格;
- 使用易于检索的变量名;
(B)函数参数使用默认值
// bad
function writeForumComment(subject, body) {
subject = subject || 'No Subject';
body = body || 'No text';
}
// good
function writeForumComment(subject = 'No subject', body = 'No text') {
// todo
}
(B)使用 async/await 函数
// bad
const request = require('request-promise').get('api_url');
request.then(response => {
console.log(response);
});
// good
async function getCleanCodeArticle() {
try {
let request = await require('request-promise');
let response = await request.get('api_url');
let fileHandle = await require('fs-promise');
await fileHandle.writeFile('article.html', response);
console.log('File written');
} catch (err) {
console.log(err);
}
}
(B)优先使用 const
对于明确不会发生变化的值,优先使用 const 进行变量声明,可避免出现对该值的意外修改的情况。
// bad
let PI = 3.14;
// good
const PI = 3.14;
(B)单行代码长度不要超过 160 个字符
过长的单行代码会造成代码的可阅读性下降,需要进行单行代码长度的限定。
(B)注释
在以下情况推荐使用注释对代码进行说明:
- 仅靠名称仍不能完全表明其意义的常量、函数、类
- 逻辑难以理解,需要注释辅助说明其用意的代码;
- 可能存在错误的代码;
- 浏览器相关的 hack 代码;
- 业务逻辑强相关的代码;
// bad
function hashIt(data) {
// The hash
let hash = 0; // Length of string
let length = data.length; // Loop through every character in data
for (let i = 0; i < length; i++) {
// Get character code.
let char = data.charCodeAt(i); // Make the hash
hash = (hash << 5) - hash + char; // Convert to 32-bit integer
hash = hash & hash;
}
}
// good
function hashIt(data) {
let hash = 0;
let length = data.length;
for (let i = 0; i < length; i++) {
let char = data.charCodeAt(i);
hash = (hash << 5) - hash + char; // Convert to 32-bit integer
hash = hash & hash;
}
}
(B)多行注释格式
最少三行, *
后跟一个空格。
/*
* one space after '*'
*/
const num = 1;
(B)以按需加载的方式使用第三方库
尽量减少第三方库的引用大小,保证打包文件尺寸在可控范围之内。
大多数主流的第三方库都提供了开箱即用的按需加载机制。
// bad
import _ from 'lodash';
// good
import pick from 'lodash/pick';
(B)使用第三方库来处理日期
日期处理属于极为常见的需求,且成熟工具数量众多,自己造轮子反而会产生不必要的风险。
// bad
function format(date) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year}-${month}-${day}`;
}
// good
import dayjs from 'dayjs';
dayjs().format('YYYY-MM-DD');
(B)对象的属性总是使用简写方式
updated
属性简写使得代码更简洁,当有简写属性与非简写属性同时存在时,避免简写属性与非简写属性的混合排列。
const name = 'sam';
const age = 12;
// bad
const person = {
name: name
};
// good
const person = {
name
};
// bad
const person = {
name,
sex: 'male',
age,
country: 'china'
};
// good
const person = {
name,
age,
sex: 'male',
country: 'china'
};
(B)封装复杂的判断条件
// bad
if (fsm.state === 'fetching' && isEmpty(listNode)) {
// / ...
}
// good
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
(B)在必要的情况才引用 this
// bad
mounted() {
const that = this;
setTimeout(() => {
that.doSomething();
}, 1000);
}
// good
mounted() {
setTimeout(() => {
this.doSomething();
}, 1000);
}
(C)避免“否定情况”的判断
// bad
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}
// good
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
(C)setInterval 的使用
谨慎使用 setInterval,尤其是在回调中存在网络请求的情况,网络情况不佳时,出现请求堆积的风险会大大增加,并影响后续的数据展示、处理等。
// bad
setInterval(request, 1000);
// good
function longRequest() {
request().then(result => {
setTimeout(longRequest, 1000);
});
}
TypeScript
(A)命名
- 属性、函数使用 camelCase 命名
- 类型使用 PascalCase 命名。
- 不要使用I做为接口名前缀。
- 枚举值使用 PascalCase 命名。
- 不要为私有属性名添加_前缀,使用private修辞符。
- 尽可能使用完整的单词拼写命名。
- 文件名使用PascalCase 命名
// Bad
const Foo = 1
// Good
const foo = 1
// Bad
const UserList:string[] = []
// Good
const userList:string[] = []
// Bad
function Foo() {}
// Good
function foo() {}
// Bad
class Foo {
Bar: number;
Baz(): number {}
}
// Good
class Foo {
bar: number;
baz(): number {}
}
接口
接口本身使用 PascalCase 方式命名,不要在接口名前加I。接口成员使用 camelCase 方式命名。
// Bad
interface IFoo {
Bar: number;
Baz(): number;
}
// Good
interface Foo {
bar: number;
baz(): number;
}
// Bad
enum status {}
// Good
enum Status {}
// Bad
enum Status {
success = 'success'
}
// Good
enum Status {
Success = 'success'
}
(A)类型声明
在进行类型声明时应尽量依靠 TS 的自动类型推断功能,如果能够推断出正确类型尽量不要再手动声明。
变量
基础类型变量不需要手动声明类型。
let foo = 'foo'
let bar = 2
let baz = false
引用类型变量应该保证类型正确。
// Bad
let bar = [] // any[]
// Good
let bar:number[] = []
(A)回调函数
不要为返回值会被忽略的回调函数设置返回值类型any,应该为返回值会被忽略的回调函数设置返回值类型void
// Bad
function fn(x: () => any) {
x();
}
// Good
function fn(x: () => void) {
x();
}
(A)使用联合类型
不要仅因某个特定位置上的参数类型不同而定义重载,应该尽可能地使用联合类型:
// Bad
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}
// Good
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}
(A)类成员
类成员声明时除了public成员,其余成员都应该显式加上作用域修辞符。
// Bad
class Foo {
foo = 'foo'
bar = 'bar'
getFoo() {
return this.foo
}
}
const foo = new Foo()
foo.foo
foo.bar
// Good
class Foo {
private foo = 'foo'
bar = 'bar'
getFoo() {
return this.foo
}
}
const foo = new Foo()
foo.getFoo()
foo.bar
(B)对象里面尽可能不用 【any】,定义成一个 BO 对象
bad
1、private knowInfo!: any;
2、private spaceInfo!: any;
3、private parentLevelId!: any;
4、public handleDropdownCommand(node: any, data: any) {
this.curEditNode = data;
}
CSS
css、less、scss 编写规范的详细说明。
(A)命名
- 类名使用小写字母,以多个单词以
-
分隔; - id 使用 camelCase 命名;
- scss、less 中的变量、函数、混合、placeholder 采用 camelCase 命名;
- 不能使用拼音;
- 单个单词不能使用缩写;
- 不得出现拼写错误;
/* bad */
.HeaderTitle
#UserName
#user-name
.biaoge
.filed
.bg
/* good */
.header-title
#userName
.table
.field
.background
(A)颜色
- 普通颜色使用 16 进制表示,字母均小写;
- 带透明度的颜色使用 rgba 表示;
/* bad */
color: #3DFEAE;
color: #1b2b351a;
/* good */
color: #3dfeae;
color: rgba(27, 43, 53, 0.1);
(A)属性简写
以下属性不得简写:background、animation、flex、font。
参考:属性简写 - MDN
/* bad */
background: #000 url(images/bg.gif) no-repeat top right;
/* good */
background-color: #000;
background-image: url(images/bg.gif);
background-repeat: no-repeat;
background-position: top right;
/* bad */
flex: 0 1 auto;
/* good */
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
/* bad */
font: italic bold 0.8em/1.2 Arial, sans-serif;
/* good */
font-style: italic;
font-weight: bold;
font-size: 0.8em;
line-height: 1.2;
font-family: Arial, sans-serif;
(A)属性值为 0 时,不加单位
/* bad */
width: 0px;
margin: 0px;
/* good */
width: 0;
margin: 0;
(A)不能使用!important
实际开发过程中,绝大多数情况下的样式需求不使用!important
即可实现。
(A)不能使用行内样式
这里指的是固定的行内样式,行内样式代码的维护性几乎为 0,极不推荐使用。需要通过程来控制的,动态的行内样式除外。
(A)选择器层级不要超过 5 层
选择器层级过深对于样式代码的可读性是灾难,对于浏览器的解析也带来很大负担,如果有超过 5 层的情况,请重新考虑选择器层级是否设计得合理。
(B)优先使用 class 选择器,避免使用元素选择器
/* bad */
div
/* good */
.class
(B)媒体查询
尽量将媒体查询的规则靠近与他们相关的规则,不要将他们一起放到一个独立的样式文件中,或者丢在文档的最底部,这样做只会让大家以后更容易忘记他们。
VUE
vue 规范的详细说明。
(A)组件名为多个单词
组件名是指组件的 name 属性,而不一定是组件文件名。
参考:name - vuejs
// bad
export default {
name: 'Content'
// ...
};
// good
export default {
name: 'MainContent'
// ...
};
(A)属性定义必须包含类型与默认值
// bad
export default {
props: ['status']
// ...
};
// good
export default {
props: {
status: {
type: String,
default: ''
}
}
// ...
};
(A)避免 v-for 与 v-if 同时使用
<!-- bad -->
<template>
<ul>
<li v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>
</ul>
</template>
<!-- good -->
<template>
<ul>
<li v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>
</template>
(A)为组件样式设置作用域
覆盖子组件样式优先使用深度选择器。
<!-- bad -->
<template>
<button class="btn btn-close">X</button>
</template>
<style>
.btn-close {
background-color: red;
}
</style>
<!-- good -->
<template>
<button class="button button-close">X</button>
</template>
<!-- 使用 `scoped` attribute -->
<style scoped>
.button {
border: none;
border-radius: 2px;
}
.button-close {
background-color: red;
}
</style>
(A)不使用自闭和组件
模板中的组件名始终为小写字母+短横线。
<template>
<!-- bad -->
<my-component />
<MyComponent />
<!-- good -->
<my-component></my-component>
</template>
(A)组件名不得缩写
// bad
components/
|- SdSettings.vue
|- UProfOpts.vue
// good
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
(A)属性命名
- 定义时,使用 camelCase 命名;
- 使用时,使用 kebab-case 命名;
<!-- bad -->
<template>
<welcome-message greetingText="hi"></welcome-message>
</template>
<script>
export default {
props: {
'greeting-text': String
}
};
</script>
<!-- good -->
<template>
<welcome-message greeting-text="hi"></welcome-message>
</template>
<script>
export default {
props: {
greetingText: {
type: String,
default: ''
}
}
};
</script>
(A)单文件组件
单文件组件中的标签顺序应为:
template
script
style
style
标签的属性只能有 lang
、scoped
、module
3 个。
<!-- bad -->
<template>
<div class="test">test</div>
</template>
<style ref="stylesheet" lang="less" scoped>
.test {
width: 100px;
}
</style>
<script>
export default {
name: 'test'
};
</script>
<!-- good -->
<template>
<div class="test">test</div>
</template>
<script>
export default {
name: 'test'
};
</script>
<style lang="less" scoped>
.test {
width: 100px;
}
</style>
(A)多个属性分多行书写,每个属性一行
<template>
<!-- bad -->
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">
<!-- good -->
<img src="https://vuejs.org/images/logo.png"
alt="Vue Logo">
</template>
(A)进行路由跳转时,只能使用 name 属性
// bad
router.push('/route-path');
// good
router.push({ name: 'routeName' });
(A)异步组件
在以下场景使用异步组件:
- 定义路由时;
- 引用依赖或内容很多的组件时;
// bad
import HeavyPage from '../views/HeavyPage.vue';
{
path: '/page-with-heavy-code',
component: HeavyPage
}
// good
{
path: '/page-with-heavy-code',
component: () => import('../views/HeavyPage.vue')
}
// bad
import HeavyComponent from './HeavyComponent.vue';
export default {
// ...,
components: {
HeavyComponent
}
}
// good
export default {
//...,
components: {
HeavyComponent: () => import('./HeavyComponent.vue')
}
}
(A)data 中只存放会变化的数据
不会变化的数据,并不需要实时响应其变化,应放到 computed
或其他文件里。
// bad
export default {
data() {
return {
rules: {
name: [{ required: true }]
}
};
}
};
// good
export default {
computed: {
rules() {
return {
name: [{ required: true }]
};
}
}
};
(A)路由命名
name
使用 camelCase 命名;path
使用 kebab-case 命名;
// bad
{
name: 'RouteName',
path: '/routePath'
}
// good
{
name: 'routeName',
path: '/route-path'
}
(B)基础组件名
应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。
// bad
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
// good
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
(B)单例组件名
只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。
// bad
components/
|- Heading.vue
|- MySidebar.vue
// good
components/
|- TheHeading.vue
|- TheSidebar.vue
(B)紧密耦合的组件名
和父组件紧密耦合的子组件应该以父组件名作为前缀命名。
// bad
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
// good
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
(B)组件名中的单词顺序
组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。
// bad
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
// good
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
(B)避免使用 javascript 直接操作 dom
<!-- bad -->
<template>
<div class="editor"></div>
</template>
<script>
export default {
mounted() {
document.querySelector('.editor').innerHTML = '';
}
};
</script>
<!-- good -->
<template>
<div class="editor">{{ text }}</div>
</template>
<script>
export default {
data() {
return {
text: ''
};
}
mounted() {
this.text = '';
}
}
</script>
(B)html 标签
尽量遵循 HTML 标准和语义,但是不应该以浪费实用性作为代价;任何时候都要用尽量小的复杂度和尽量少的标签来解决问题。
(C)计算属性
把复杂的计算属性分割为尽可能多的更简单的属性。
<!-- bad -->
<template>
<div>
{{
fullName
.split(' ')
.map(function(word) {
return word[0].toUpperCase() + word.slice(1);
})
.join(' ')
}}
</div>
</template>
<!-- good -->
<template>
<div>
{{ normalizedFullName }}
</div>
</template>
<script>
export default {
// 复杂表达式已经移入一个计算属性
computed: {
normalizedFullName: function() {
return this.fullName
.split(' ')
.map(function(word) {
return word[0].toUpperCase() + word.slice(1);
})
.join(' ');
}
}
};
</script>
其他
(B)单个文件代码行数不超过 500 行
多于 500 行代码的单个文件在可读性、可维护性上都会给使用者带来一定的负面情绪,应充分考虑如何分割代码。
(A)网络请求的错误提示(如果是根据后端返回的提示),使用 transformError2JSON
(A)网络请求 RequestUtil.sendRequest 在 then 里面不需要通过 res.code === 0 来判断成功,then 都是网络请求成功
附录
参考文档
更多推荐
所有评论(0)