Web前端代码风格规范

398 阅读5分钟

每个团队的成员都是团队的一部分,产出与维护总是相互交织的,你种的树可能以后他也会乘凉,他埋的雷可能有一天会被你踩,为了让我们都成为互帮互助的“种树人”,所以这些各种写法皆可的情况下,希望通过一个统一的规范,来指导大家,方便大家编码,也方便后续的阅读维护

1、变量命名

团队的编码命名风格是小驼峰

// 大部分项目的Eslint的驼峰检查都会开着
//"camelcase": ["error", { "properties": "always" }]

// Bad:
import { no_camelcased } from "external-module"

var my_favorite_color = "#112C85";

obj.do_something = function() {
    // ...
};

function foo({ isCamelcased: no_camelcased }) {
    // ...
}

var { category_id = 1 } = query;

// Good:
import { no_camelcased as camelCased } from "external-module";

var myFavoriteColor   = "#112C85";
var _myFavoriteColor  = "#112C85";
var myFavoriteColor_  = "#112C85";
var MY_FAVORITE_COLOR = "#112C85";

var { category_id: category } = query;

function foo({ isCamelCased }) {
    // ...
};

function foo({ isCamelCased: isAlsoCamelCased }) {
    // ...
}

var { categoryId = 1 } = query;

用有意义且常用的单词命名

也要注意拼写问题,避免拼写错误,推荐VS扩展 Code Spell Checker

// Bad:
const yyyymmdstr = moment().format('YYYY/MM/DD');

const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/;
saveCityZipCode(
 address.match(cityZipCodeRegex)[1], // 这个公式到底要干嘛,对不起,原作者已经离职了。自己看代码
 address.match(cityZipCodeRegex)[2], // 这个公式到底要干嘛,对不起,原作者已经离职了。自己看代码
);

// Good:
const currentDate = moment().format('YYYY/MM/DD');

const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\]+[,\s]+(.+?)s*(d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);

避免无意义的变量名前缀

例如:如果创建了一个对象car,那么就没有必要把他的颜色命名为carColor

// Bad:
const car = {
    carMake: 'Honda',
    carModel: 'Accord',
    carColor: 'Blue'
};
function paintCar( car ) {
    car.carColor = 'Red';
}

// Good:
const car = {
    make: 'Honda',
    model: 'Accord',
    color: 'Blue'
};
function paintCar( car ) {
    car.color = 'Red';
}

传参使用默认值

es6的新feature 函数扩展与解构赋值默认值

// Bad:
function createMicrobrewery( name ) {
    const breweryName = name || 'Hipster Brew Co.';
    // ...
}
// Good:
function createMicrobrewery( name = 'Hipster Brew Co.' ) {
    // ...
}

变量赋值考虑好取不到的情况

// Bad:
const MIN_NAME_LENGTH = 8;
let lastName = fullName[1];
if(lastName.length > MIN_NAME_LENGTH) { 
// 这样你就给你的代码成功的埋了一个坑,你有考虑过如果fullName = ['jackie']这样的情况吗?
// 这样程序一跑起来就爆炸。要不你试试。
   ....
}

// Good: 
const MIN_NAME_LENGTH = 8;
let lastName = fullName[1] || '';  
// 做好兜底,fullName[1]中取不到的时候,不至于赋值个undefined,至少还有个空字符,从根本上讲,
// lastName的变量类型还是String,String原型链上的特性都能使用,不会报错。不会变成undefined。

if(lastName.length > MIN_NAME_LENGTH) {
   ....
}
// 其实在项目中有很多求值变量,对于每个求值变量都需要做好兜底。

let propertyValue = Object.attr || 0; // 因为Object.attr有可能为空,所以需要兜底。

// 但是,赋值变量就不需要兜底了。
let a = 2; // 因为有底了,所以不要兜着。
let myName = 'Tiny'; // 因为有底了,所以不要兜着。

2、抽取常量

所有无法一眼看清目的的数字都抽取常量

// Bad:
setTimeout( blastOff, 86400000 ); // 其他人知道 86400000 的意思吗?

if (value.length < 8) { // 为什么要小于8,8表示啥?
   ....
}

// Good:
const MILLISECOND_IN_A_DAY = 86400000;
setTimeout( blastOff, MILLISECOND_IN_A_DAY );

const MAX_INPUT_LENGTH = 8;
if (value.length < MAX_INPUT_LENGTH) { // 一目了然,不能超过最大输入长度
   ....
}

全局常量统一管理

1、保证可读性, 把数字变成有意义的常量,可以直接看懂你要传的1 是啥意思。
2、方便统一管理。 这个其实更重要。 有些后端也在用的映射关系,
举个例子比如说什么

1:未审核,2:合格:3,不合格, 同一业务的不同页面里很多地方都用了,那就在const里面维护一个映射关系就行了, 所有用到这个状态的地方,都从const里面取。
因为有这种后面改映射的情况, 比如果说3不用做不合格了, 要改成5, 要是不统一管理, 前端改动的成本可能就非常高了。

再次强调使用正确的单词、缩写

// Bad:
let fName = 'jackie'; // 看起来命名挺规范,缩写,驼峰法都用上,ESlint各种检测规范的工具都通过,But,fName是啥?
let lName = 'willen'; // 这个问题和上面的一样

// Good:
let firstName = 'jackie'; // 怎么样,是不是一目了然。少被喷了一次
let lastName = 'willen';

3、函数

从函数命知返回值类型

// Bad:
function showFriend() {
 ... //  完全想不到这个函数要干嘛,不知道返回的是啥
}

// Good:
function getFriendList() { ...} // 可以知道返回值是一个数组

// 对于返回true  or  false的函数,最好以should/is/can/has开头
function isEmpty() {...}
function canCreateDocuments() {...}
function hasLicense() {...}

// 也有一些特殊的常用函数
function renderTableHeader() {
	... // return a reactNode
}

函数传参最好是对象而非位置映射

更不要混合使用,前面是对象,接着就又是位置映射。

// Bad:
page.getSVG(api, true, false); // true和false啥意思,一目不了然


// Good:
page.getSVG({
   imageApi: api,
   includePageBackground: true, // 一目了然,知道这些true和false是啥意思
   compress: false,
})

一个方法只做一件事情

这是一条在软件工程领域流传久远的规则。严格遵守这条规则会让你的代码可读性更好,也更容易重构。如果违反这个规则,那么代码会很难被测试或者重用。

// Bad:
function sendEmailToClients(clients) {
 clients.forEach(client => {
   const clientRecord = database.lookup(client)
   if (clientRecord.isActive()) {
     email(client)
   }
 })
}

// Good:
function sendEmailToActiveClients(clients) {  //各个击破,易于维护和复用
 clients.filter(isActiveClient).forEach(email)
}

function isActiveClient(client) {
 const clientRecord = database.lookup(client)
 return clientRecord.isActive()
}

删除不用的代码

你自己的代码,PM说这次版本不用了,不要注释掉,直接删除!不要相信他们“以后还会用的”这种话,记住历史代码这件事我们已经交给git了,你要做的是保证当前使用代码的整洁。

4、文件

@ 引用优化 

文件的引入三层及以上用  @ 进行引用优化 

// 各个项目的根目录 jsconfig.json都有配置
{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

// Bad:
import { removeExcellentAlbum } from '../../../../actions/AlbumActions';

// Good:
import { openActionSheet } from '@/actions/CommonActions';

5、写法

异常逻辑处理

渲染函数如果有return  null的情况要优先返回提升可读性【判断的异常条件个数<=2】。

// Bad:
const renderModal = () => {
	if(isNeedShow) {
		return(
			<div>
				......
				......
			<div>
  		)
	}
	return null
}

// Good: 
const renderModal = () => {
	if(!isNeedShow) {
		return null
	}
	return(
		<div>
			......
			......
		</div>
  	)
}

三目运算符

三目两个语句体的内容不宜过大[不回行,不超长度]。

// Bad:
render(){
	return(
		<li>
		{
		isShowTitle
		?(
			<Title 
				...
				...
			/>
		)
		:(
			<Notitle 
				...
				...
			/>
			)
		}
		</li>
	)
}

// Good:
① 简写内容
render(){
	return(
		<li>{isShowTitle?‘标题’:‘无标题’}</li>
	)
} 
② 抽取渲染函数
render(){
	return(
		<li>{isShowTitle? renderTitle():renderNoTitle}</li>
	)
}

三目的条件语句中的条件个数不超过2个[超过的要抽取render函数]。

// Bad:
render(){
	return(
		<li>{(isShowTitle && titleList && titleList.length > 2) ? renderTitle():renderNoTitle}</li>
	)
}

// Good:

const renderTitle = () => {
	if(isShowTitle && titleList && titleList.length > 2) {
		return(
			<Title 
				...
				...
			/>
		)
	}
	return (
		<Notitle 
			...
			...
		/>
	)
}

render(){
	return(
		<li>{renderTitle()}</li>
	)
}

Redux 相关

①:Components 尽量设计为不需要 connect to store,连接store 的 component 应为 container。page 需要有组件的拆解,且原则上1个 component 文件长度最大不应超过500行。

②:ActionTypes 统一存放在 src/const/ActionTypes 里。不同的模块创建不同的 CT function 去给 action type name 加前缀,如:

const userCT = type => `@user/${type}`;

export const LOGIN = userCT('LOGIN');

③:使用 redux 的 connect 函数统一采用 @ (decorators) 的写法

@connect(mapStateToProps, mapDispatchToProps)
class CustomerComponent extends React.Componet {}

export default CustomerComponent;

其他部分

还有一些格式性质的问题,个人认为没有必要按照文档的方式维护,editor-config、prettier、eslint、stylelint,最后再加上husky校验,这些足以保证团队内代码格式一致,样式写法一致。

当前规范并非全部是原创,特别是前半部分的用例,也参考了前人的经验,后面部分的规定更加偏向于原创。
当然这套组合可能并非非常适合你,比如命名方式可能在你的团队内更偏向于大驼峰甚至是下划线。
但是重要的是有了统一的风格,不论是阅读还是维护,绝对会让参与者有更好的“游戏体验”。