写在开头
- 本文主要整理一些日常开发使用频率较多的规范,例如:文件结构、命名。仅为作者个人观点。
相关文章
- ES6常用但被忽略的方法(第一弹解构赋值和数值)
- ES6常用但被忽略的方法(第二弹函数、数组和对象)
- ES6常用但被忽略的方法(第三弹Symbol、Set 和 Map )
- ES6常用但被忽略的方法(第四弹Proxy和Reflect)
- ES6常用但被忽略的方法(第五弹Promise和Iterator)
- ES6常用但被忽略的方法(第六弹Generator )
- ES6常用但被忽略的方法(第七弹async)
- ES6常用但被忽略的方法(第八弹Class)
- ES6常用但被忽略的方法(第九弹Module)
- ES6常用但被忽略的方法(第十一弹Decorator)
- ES6常用但被忽略的方法(终弹-最新提案)
文件结构
- 在日常项目开发中,大部分的开发人员是不需要去关注整个项目的结构目录,因为项目的负责人,或者其他的架构人员已经把整个项目文件结构弄好了。开发人员只需要在自己负责的模块目录去编写对应的模块即可。那如果需要你去搭建一个项目,你应该怎么去设计文件结构呢?
react
项目为例
- 项目名称为
detanx-react
。
入口
detanx-react/index.js
文件,项目的入口文件。
配置目录
detanx-react/config
文件夹,用来存放项目的一些配置文件,例如我们使用webpack
的打包配置以及其他比如将react-router
、react-dom
等通过gulp打包单独的包等配置。
打包分析目录
detanx-react/analyz
文件夹,用来存放通过webpack
打包后,分析打包过程的文件。
生产目录
detanx-react/build
或者detanx-react/dist
文件夹,用来存放项目开发完成后,需要部署上线的所有生产文件。
数据目录
detanx-react/mock
文件夹,在项目开发时,后端开发的进度可能没有前端快,导致接口无法调试,所以我们可以自己配置mock
数据进行开发和调试。这个文件夹就用来存放所有的mock
数据。
公共目录
detanx-react/public
文件夹,打包会需要基本的html
模版,内嵌的一些比如css
、favicon
等我们就可以放到这个文件夹下面。
引用目录
detanx-react/src
文件夹,这个文件夹是我们项目开发变化最多的,我们写的大多数代码都在这个文件夹下面,针对不同项目,它下面的结构也不一样。detanx-react/src/components
文件夹,存放项目开发的公共组件。detanx-react/src/hooks
文件夹,存放开发过程中封装的一些新的hook。detanx-react/src/request
文件夹,存放项目请求的封装。detanx-react/src/router
文件夹,存放项目的路由相关设置,配置也可以写到detanx-react/config
文件夹下。detanx-react/src/view
文件夹,存放项目开发的所有页面内容,根据每个模块划分。- 每个模块中又可以分为
index
文件(模块入口)、components
文件夹(存放模块的抽离组件)等。
- 每个模块中又可以分为
detanx-react/src/common
文件夹,存放项目的公共内容,例如:公共的方法(判断空、数据类型等)、常量(会多个(2
个及以上)不同模块用到的常量)文件等。
静态目录
detanx-react/static
文件夹,这个文件夹也可以放在detanx-react/src
下面,用于存放一些图片、字体、CSS
(LESS
、SASS
)等文件。
其他
- 根据自己的项目需求在任意目录下可以适当添加其他文件夹。
命名
- 命名部分主要包括了函数命名、变量命名、模块命名、
class
命名(CSS
命名)。开发项目时,尤其在多人合作的时候,如果没有一个好的命名规范并且也不喜欢写代码注释。当你离职或者其他原因需要项目交接时,别人可能就是在遍看代码边问候你的家人了,可能还会打电话问你,浪费彼此很多时间。
函数命名
- 命名方式可以用大驼峰、小驼峰。一般建议使用小驼峰。
- 我们在写一个函数的时候,我们首先应该确定该函数的一个功能,比如判断一个值的类型、通过请求获取一个某个表格的数据、显示或隐藏某个页面等。所以根据不同的功能我们可以用不同的单词开始,例如:
can
判断是否可执行某个动作(canGetUserName
)has
判断是否含有某个值(hasUserName
)is
判断是否为某个值(isEmpty
)get
获取某个值(getUserName
)set
设置某个值(setUserName
)load
加载某些数据(loadUserInfo
)
- 这几个肯定不可能完整的覆盖项目中所有的场景,当我们函数命名不能直接看出在做什么的时候,我们可以通过给函数添加注释来说明函数的作用。
- 每个函数应该都是一个独立的功能,降低函数之间的耦合。
- 每个函数的大小不宜超过
200
行,超过应该尝试抽离部分逻辑为一个新的函数。实在无法抽离应该在复杂逻辑部分添加适当的注释。 - 私有方法可以在项目开发的时候,开发人员之间做一个规范,例如使用
_
或$
开头的函数为私有。
变量命名
- 在项目开发的时候,很多开发人员为了省事,就对一个变量随便取名。例如:可能只是用来接收一个函数的返回值,就取个
a
,反正下面只会用到一次;再者一个是某个计数变量就直接一个i
或者其他字母。当你这个模块全身这种变量时,别人看你的代码就是一个灾难。 - 普通变量我们可以使用var或者let去声明,在命名一个变量时,我们应该通过变量的类型或者它的使用场景去命名,尽量做到明确语义。例如:
- 布尔类型:
isShowModal
(是否显示弹窗) - 数组类型:
userLists
(用户列表) - 对象类型:
userInfoObj
(用户信息对象,虽然我们一般知道userInfo
就是一个对象,我这里只是表示一个如果看不出来的命名,我们可以在后面加一个标识) Symbol
类型:userNameSym
(Symbol
类型的用户名)- 等等
- 布尔类型:
- 变量命名必须以字母、下划线
_
或者$
为开头,通常开发时约定_
或者$
开头的为私有变量。 - 变量命名时应该尽量保证命名中不出现数字,例如:
list1
、list2
等等这种变量名称。 - 常量应该全部大些并且每个单词以下划线连接(
WARNING_DUATION_TIME
:警告显示时间)。
模块命名
- 以
react
模块为例,我们每个模块的命名应该使用大驼峰(UserConfig
),并且模块名称应该语义化,表示模块的内容,例如(UserConfig
)表示用户的配置模块。
class
命名(CSS
命名)
CSS
命名我们一般是使用BEM
(块(block
)、元素(element
)、修饰符(modifier
)) 命名规范。.block {} .block__element {} .block--modifier {}
block
代表了更高级别的抽象或组件。block__element
代表.block
的后代,用于形成一个完整的.block
的整体。block--modifier
代表.block
的不同状态或不同版本。
- 使用两个连字符和下划线而不是一个,是为了让你自己的块可以用单个连字符来界定。
.sub-block__element {} .sub-block--modifier {}
- 优缺点:
- 优点:可以获得更多的描述和更加清晰的结构,从其名字可以知道某个标记的含义。
- 缺点:当嵌套太深,
class
名称会特别长,我们可以通过其他约定解决,例如:缩减层级等。
- 使用
- 时机:需要明确关联性的模块关系时。
- 处理:通过
LESS/SASS
等预处理器语言来编写CSS
。
代码规范
- ES6-编程风格
- 在开发中,如果没有使用一些代码规范的插件,多个开发人员写得代码就几个风格,所以协同开发时,我们需要对代码的规范进行约定,例如
js
通过eslint
去约束,ts
可以通过tslint
约束,我们只需要一个人去配置相应的约束的配置文件。 - 但有些代码的编写这些约束也做不到,只是实现的好坏而已。下面举一些代码上的规范(不一定是约束无法做到的)。
ESLint 的使用示例
ESLint
是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。- 安装
ESLint
。
$ npm i -g eslint
- 安装
Airbnb
语法规则,以及import
、a11y
、react
插件。
$ npm i -g eslint-config-airbnb
$ npm i -g eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
- 在项目的根目录下新建一个
.eslintrc
文件,配置ESLint
。
{
"extends": "eslint-config-airbnb"
}
vscode
中安装eslint
插件即可边写边检查。
引号
- 引号分为单引号(
'
)和双引号("
)以及ES6
新增的模版字符串使用 `` 表示,可能在开发时大部分都是随便去使用这个引号,怎么输入着方便怎么来。一般情况下,.js
、.ts
、.jsx
、.tsx
、.vue
等可以写js
的文件中,一般是使用单引号('
),在html
中(标签的属性等)使用双引号('
)。``可以代替以前的连续字符串连接操作。
export default class extends PureComponent {
constructor() {
this.state = {
value: 'detanx',
list: null
}
}
render() {
const { value, list } = this.state
return (<>
<div className="detanx">{value}</div>
</>)
}
}
// ``使用
let user = 'my name is'
const NAME = 'detanx'; // 简单示例一下
// es5
user = user + ' ' + NAME;
// es6
let user = `${user} ${NAME}`
变量
- 变量命名规范在上面说过了,这里说一下变量的使用。我们现在编写代码应该尽量的使用
let
和const
去声明变量,而不是用var
。 - 所有变量都应该先声明再使用。
- 超过
2
处使用的某个值,我们应该使用const
在当前使用模块的最顶层去声明,多个模块使用的应该在文件的公共模块的常量文件中声明。 - 声明一个变量应该给它赋一个初值。
// bad
let i
for(i = 0; i < 10; i ++) { ... }
// good
let i = 0;
for(; i < 10; i ++) { ... }
for (let i = 0; i < 10; i ++) { ... }
- 使用
const
连续声明多个值时,可以使用数组解构赋值的方式。
// bad
var a = 1, b = 2, c = 3;
// good
const a = 1;
const b = 2;
const c = 3;
// best
const [a, b, c] = [1, 2, 3];
解构赋值
- 可以使用解构赋值的地方优先使用解构赋值。例如上面的多个
const
声明。- 数组成员对变量赋值时,优先使用解构赋值。
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr;
- 函数的参数如果是对象的成员,优先使用解构赋值。
// bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; } // good function getFullName(obj) { const { firstName, lastName } = obj; } // best function getFullName({ firstName, lastName }) { }
- 如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
// bad function processInput(input) { return [left, right, top, bottom]; } // good function processInput(input) { return { left, right, top, bottom }; } const { left, right } = processInput(input);
对象
- 单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
// bad
const a = { k1: v1, k2: v2, };
const b = {
k1: v1,
k2: v2
};
// good
const a = { k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};
- 对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用
Object.assign
方法。
// bad
const a = {};
a.x = 3;
// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
- 如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
-
上面代码中,对象
obj
的最后一个属性名,需要计算得到。这时最好采用属性表达式,在新建obj
的时候,将该属性与其他属性定义在一起。这样一来,所有属性就在一个地方定义了。 -
对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
var ref = 'some value';
// bad
const atom = {
ref: ref,
value: 1,
addValue: function (value) {
return atom.value + value;
},
};
// good
const atom = {
ref,
value: 1,
addValue(value) {
return atom.value + value;
},
};
数组
使用扩展运算符(...
)拷贝数组。
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
- 使用
Array.from
方法,将类似数组的对象转为数组。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
函数
- 匿名函数当作参数的场合,尽量用箭头函数代替。
// 立即执行函数
(() => {
console.log('Welcome to the Internet.');
})();
- 所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。
// bad
function divide(a, b, option = false ) { ... }
// good
function divide(a, b, { option = false } = {}) { ... }
- 不要在函数体内使用
arguments
变量,使用rest
运算符(...
)代替。因为rest
运算符显式表明你想要获取参数,而且arguments
是一个类似数组的对象,而rest
运算符可以提供一个真正的数组。
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
- 使用默认值语法设置函数参数的默认值。
// bad
function handleThings(opts) {
opts = opts || {};
}
// good
function handleThings(opts = {}) { ... }
Map 结构
- 注意区分
Object
和Map
,只有模拟现实世界的实体对象时,才使用Object
。如果只是需要key: value
的数据结构,使用Map
结构。因为Map
有内建的遍历机制。
let map = new Map(arr);
for (let key of map.keys()) { console.log(key); }
for (let value of map.values()) { console.log(value); }
for (let item of map.entries()) { console.log(item[0], item[1]); }
模块
- 使用
import
取代require
。
// bad
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// good
import { func1, func2 } from 'moduleA';
- 使用
export
取代module.exports
。
// commonJS的写法
const React = require('react');
const Breadcrumbs = React.createClass({
render() {
return <nav />;
}
});
module.exports = Breadcrumbs;
// ES6的写法
import React from 'react';
class Breadcrumbs extends React.Component {
render() {
return <nav />;
}
};
export default Breadcrumbs;
-
如果模块只有一个输出值,就使用
export default
,如果模块有多个输出值,就使用export
,export default
与普通的export
不要同时使用。 -
不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(
export default
)。
// bad
import * as myObject from './importModule';
// good
import myObject from './importModule';
- 模块默认输出一个函数,函数名的首字母应该小写。默认输出一个对象,对象名的首字母应该大写。
function makeStyleGuide() { ... }
export default makeStyleGuide;
const StyleGuide = {
es6: {
}
};
export default StyleGuide;