ES6常用但被忽略的方法(第十弹项目开发规范)

1,219

写在开头

  • 本文主要整理一些日常开发使用频率较多的规范,例如:文件结构、命名。仅为作者个人观点。

相关文章

文件结构

  • 在日常项目开发中,大部分的开发人员是不需要去关注整个项目的结构目录,因为项目的负责人,或者其他的架构人员已经把整个项目文件结构弄好了。开发人员只需要在自己负责的模块目录去编写对应的模块即可。那如果需要你去搭建一个项目,你应该怎么去设计文件结构呢?

react项目为例

  • 项目名称为detanx-react
入口
  • detanx-react/index.js文件,项目的入口文件。
配置目录
  • detanx-react/config文件夹,用来存放项目的一些配置文件,例如我们使用webpack的打包配置以及其他比如将react-routerreact-dom等通过gulp打包单独的包等配置。
打包分析目录
  • detanx-react/analyz文件夹,用来存放通过webpack打包后,分析打包过程的文件。
生产目录
  • detanx-react/build或者detanx-react/dist文件夹,用来存放项目开发完成后,需要部署上线的所有生产文件。
数据目录
  • detanx-react/mock文件夹,在项目开发时,后端开发的进度可能没有前端快,导致接口无法调试,所以我们可以自己配置mock数据进行开发和调试。这个文件夹就用来存放所有的mock数据。
公共目录
  • detanx-react/public文件夹,打包会需要基本的html模版,内嵌的一些比如cssfavicon等我们就可以放到这个文件夹下面。
引用目录
  • detanx-react/src文件夹,这个文件夹是我们项目开发变化最多的,我们写的大多数代码都在这个文件夹下面,针对不同项目,它下面的结构也不一样。
    1. detanx-react/src/components文件夹,存放项目开发的公共组件。
    2. detanx-react/src/hooks文件夹,存放开发过程中封装的一些新的hook。
    3. detanx-react/src/request文件夹,存放项目请求的封装。
    4. detanx-react/src/router文件夹,存放项目的路由相关设置,配置也可以写到detanx-react/config文件夹下。
    5. detanx-react/src/view文件夹,存放项目开发的所有页面内容,根据每个模块划分。
      • 每个模块中又可以分为index文件(模块入口)、components文件夹(存放模块的抽离组件)等。
    6. detanx-react/src/common文件夹,存放项目的公共内容,例如:公共的方法(判断空、数据类型等)、常量(会多个(2个及以上)不同模块用到的常量)文件等。
静态目录
  • detanx-react/static文件夹,这个文件夹也可以放在detanx-react/src下面,用于存放一些图片、字体、CSSLESSSASS)等文件。
其他
  • 根据自己的项目需求在任意目录下可以适当添加其他文件夹。

命名

  • 命名部分主要包括了函数命名、变量命名、模块命名、class命名(CSS命名)。开发项目时,尤其在多人合作的时候,如果没有一个好的命名规范并且也不喜欢写代码注释。当你离职或者其他原因需要项目交接时,别人可能就是在遍看代码边问候你的家人了,可能还会打电话问你,浪费彼此很多时间。

函数命名

  • 命名方式可以用大驼峰、小驼峰。一般建议使用小驼峰。
  • 我们在写一个函数的时候,我们首先应该确定该函数的一个功能,比如判断一个值的类型、通过请求获取一个某个表格的数据、显示或隐藏某个页面等。所以根据不同的功能我们可以用不同的单词开始,例如:
    1. can 判断是否可执行某个动作(canGetUserName
    2. has 判断是否含有某个值(hasUserName
    3. is 判断是否为某个值(isEmpty
    4. get 获取某个值(getUserName
    5. set 设置某个值(setUserName
    6. load 加载某些数据(loadUserInfo
  • 这几个肯定不可能完整的覆盖项目中所有的场景,当我们函数命名不能直接看出在做什么的时候,我们可以通过给函数添加注释来说明函数的作用。
  • 每个函数应该都是一个独立的功能,降低函数之间的耦合。
  • 每个函数的大小不宜超过200行,超过应该尝试抽离部分逻辑为一个新的函数。实在无法抽离应该在复杂逻辑部分添加适当的注释。
  • 私有方法可以在项目开发的时候,开发人员之间做一个规范,例如使用_$开头的函数为私有。

变量命名

  • 在项目开发的时候,很多开发人员为了省事,就对一个变量随便取名。例如:可能只是用来接收一个函数的返回值,就取个a,反正下面只会用到一次;再者一个是某个计数变量就直接一个i或者其他字母。当你这个模块全身这种变量时,别人看你的代码就是一个灾难。
  • 普通变量我们可以使用var或者let去声明,在命名一个变量时,我们应该通过变量的类型或者它的使用场景去命名,尽量做到明确语义。例如:
    1. 布尔类型:isShowModal(是否显示弹窗)
    2. 数组类型:userLists(用户列表)
    3. 对象类型:userInfoObj(用户信息对象,虽然我们一般知道userInfo就是一个对象,我这里只是表示一个如果看不出来的命名,我们可以在后面加一个标识)
    4. Symbol类型:userNameSymSymbol类型的用户名)
    5. 等等
  • 变量命名必须以字母、下划线_或者$为开头,通常开发时约定_或者$开头的为私有变量。
  • 变量命名时应该尽量保证命名中不出现数字,例如:list1list2等等这种变量名称。
  • 常量应该全部大些并且每个单词以下划线连接(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 {}
    
  • 优缺点:
    1. 优点:可以获得更多的描述和更加清晰的结构,从其名字可以知道某个标记的含义。
    2. 缺点:当嵌套太深,class名称会特别长,我们可以通过其他约定解决,例如:缩减层级等。
  • 使用
    1. 时机:需要明确关联性的模块关系时。
    2. 处理:通过 LESS/SASS 等预处理器语言来编写 CSS

代码规范

  • ES6-编程风格
  • 在开发中,如果没有使用一些代码规范的插件,多个开发人员写得代码就几个风格,所以协同开发时,我们需要对代码的规范进行约定,例如js通过eslint去约束,ts可以通过tslint约束,我们只需要一个人去配置相应的约束的配置文件。
  • 但有些代码的编写这些约束也做不到,只是实现的好坏而已。下面举一些代码上的规范(不一定是约束无法做到的)。

ESLint 的使用示例

  • ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。
  • 安装 ESLint
$ npm i -g eslint
  • 安装 Airbnb 语法规则,以及 importa11yreact 插件。
$ 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}`

变量

  • 变量命名规范在上面说过了,这里说一下变量的使用。我们现在编写代码应该尽量的使用letconst去声明变量,而不是用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声明。
    1. 数组成员对变量赋值时,优先使用解构赋值。
    const arr = [1, 2, 3, 4];
    
    // bad
    const first = arr[0];
    const second = arr[1];
    
    // good
    const [first, second] = arr;
    
    1. 函数的参数如果是对象的成员,优先使用解构赋值。
    // 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 }) {
    }
    
    1. 如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
    // 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 结构

  • 注意区分 ObjectMap,只有模拟现实世界的实体对象时,才使用 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,如果模块有多个输出值,就使用exportexport 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;