前端代码规范

本文最后更新于:1 年前

写在前面

很多人可能会疑惑,代码自己写的看的舒服不就得了,为什么一定要这样。是的,当你是一个人维护的时候确实是这样子,但是当多人协同开发的时候,代码规范就尤为重要,不仅仅会减少因为格式、风格不一致的代码合并记录,还能减少容易出现的 bug 和出问题的概率,更能提升自己的编码水平;当你能完全掌控自己代码的时候就会发现,有规范有约定的代码是如何优雅和易调试。所以即使是单人维护开发的项目,也需要有代码规范约定,这种约定可能来自于业界的共识,也可能来自公司的规则,也可能是个人的理解。

虽然代码规范很多地方并不能用脚本或者人为强制控制,但是也希望各位能严格遵从,有问题可以提出并讨论,但是有规范的团队才是有战斗力的。保持自律,做个牛批的 programmer


代码规范

代码一致性和最佳实践。通过代码风格的一致性,降低维护代码的成本以及改善多人协作的效率。同时遵守最佳实践,确保页面性能得到最佳优化和高效的代码。

关于项目

  1. 项目文件命名应该全小写,以横杠隔开单词的形式,因为部分文件管理器不区分大小写
  2. 文件命名应该清晰易懂,最好包含该模块的功能和属性
  3. 如果一个模块文件夹内包含多个子模块,那默认导出模块文件应取名 index

HTML

原则:html 为前端的结构层,不应将样式层和逻辑层混写进入,减少没必要的多余空格,页面结构尽可能简单明了

  1. 缩进使用 2 个空格。
  2. 所有标签都必须闭合,包括一些自闭合的标签。
  3. 使用双引号而不是单引号。
  4. 一行应最好不超过 80 个字符,如过长可以属性换行。
  5. 属性过多的(2 个或以上)可换行。
<input
  class="name-input"
  id="name-input"
  type="text"
  placeholder="请输入名字"
  maxlength="10"
/>
  1. 注释可以为大型模块标注结构起始。
<!--卡片头部-->
<div class="card-header">
  <!--...-->
  <!--大量结构-->
  <!--...-->
</div>
<!--卡片头部 END-->
  1. 书写代码前应考虑结构复用性。
通用prettier代码风格配置
{
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "proseWrap": "preserve",
  "arrowParens": "avoid",
  "bracketSpacing": true,
  "disableLanguages": ["vue"],
  "endOfLine": "auto",
  "ignorePath": ".prettierignore",
  "jsxBracketSameLine": false,
  "jsxSingleQuote": false,
  "requireConfig": false,
  "trailingComma": "es5"
}

CSS

  1. 类名使用小写字母,用横杠隔开单词。
  2. 按结构层和样式层分层的原则来说,应避免出现 html 标签选择器。
  3. 选择器应尽量简单,减少复杂嵌套。
  4. 省略 0 属性值后面的单位。
  5. 注释格式 /* 这儿是注释 */
  6. 样式应该有一定顺序性,总结来说就是盒子属性、盒子样式、行内属性、行内样式、动画。
tips: 样式顺序 stylelint
module.exports = {
  plugins: ['stylelint-order'],
  rules: {
    'property-no-unknown': true,
    'order/properties-order': [
      {
        // 重置
        properties: ['all'],
      },
      {
        // 显示类型
        properties: ['display', 'box-sizing'],
      },
      {
        // flex布局样式
        properties: [
          'flex',
          'flex-basis',
          'flex-direction',
          'flex-flow',
          'flex-grow',
          'flex-shrink',
          'flex-wrap',
        ],
      },
      {
        // grid布局样式
        properties: [
          'grid',
          'grid-area',
          'grid-template',
          'grid-template-areas',
          'grid-template-rows',
          'grid-template-columns',
          'grid-row',
          'grid-row-start',
          'grid-row-end',
          'grid-column',
          'grid-column-start',
          'grid-column-end',
          'grid-auto-rows',
          'grid-auto-columns',
          'grid-auto-flow',
          'grid-gap',
          'grid-row-gap',
          'grid-column-gap',
        ],
      },
      {
        // 列
        properties: ['align-content', 'align-items', 'align-self'],
      },
      {
        // 行
        properties: ['justify-content', 'justify-items', 'justify-self'],
      },
      {
        // 空隙
        properties: ['gap', 'row-gap', 'column-gap'],
      },
      {
        // 位置
        properties: ['position', 'top', 'right', 'bottom', 'left', 'z-index'],
      },
      {
        // 顺序
        properties: ['order'],
      },
      {
        // 盒子模型
        properties: [
          'float',
          'width',
          'min-width',
          'max-width',
          'height',
          'min-height',
          'max-height',
          'padding',
          'padding-top',
          'padding-right',
          'padding-bottom',
          'padding-left',
          'margin',
          'margin-top',
          'margin-right',
          'margin-bottom',
          'margin-left',
          'overflow',
          'overflow-x',
          'overflow-y',
          '-webkit-overflow-scrolling',
          '-ms-overflow-x',
          '-ms-overflow-y',
          '-ms-overflow-style',
          'clip',
          'clear',
        ],
      },
      {
        // 盒子样式
        properties: [
          'background',
          'background-color',
          'background-image',
          "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient",
          'filter:progid:DXImageTransform.Microsoft.gradient',
          'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader',
          'filter',
          'background-repeat',
          'background-attachment',
          'background-position',
          'background-position-x',
          'background-position-y',
          'background-clip',
          'background-origin',
          'background-size',
          'background-blend-mode',
          'isolation',
          'border',
          'border-color',
          'border-style',
          'border-width',
          'border-top',
          'border-top-color',
          'border-top-style',
          'border-top-width',
          'border-right',
          'border-right-color',
          'border-right-style',
          'border-right-width',
          'border-bottom',
          'border-bottom-color',
          'border-bottom-style',
          'border-bottom-width',
          'border-left',
          'border-left-color',
          'border-left-style',
          'border-left-width',
          'border-radius',
          'border-top-left-radius',
          'border-top-right-radius',
          'border-bottom-right-radius',
          'border-bottom-left-radius',
          'border-image',
          'border-image-source',
          'border-image-slice',
          'border-image-width',
          'border-image-outset',
          'border-image-repeat',
          'outline',
          'outline-width',
          'outline-style',
          'outline-color',
          'outline-offset',
          'box-shadow',
          'mix-blend-mode',
          'filter:progid:DXImageTransform.Microsoft.Alpha(Opacity',
          "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha",
          'opacity',
          '-ms-interpolation-mode',
        ],
      },
      {
        // 行内样式
        properties: [
          'font',
          'font-family',
          'font-size',
          'font-style',
          'font-weight',
          'font-variant',
          'font-size-adjust',
          'font-stretch',
          'font-effect',
          'font-emphasize',
          'font-emphasize-position',
          'font-emphasize-style',
          '-webkit-font-smoothing',
          '-moz-osx-font-smoothing',
          'font-smooth',
          'hyphens',
          'line-height',
          'color',
          'text-align',
          'text-align-last',
          'text-emphasis',
          'text-emphasis-color',
          'text-emphasis-style',
          'text-emphasis-position',
          'text-decoration',
          'text-indent',
          'text-justify',
          'text-outline',
          '-ms-text-overflow',
          'text-overflow',
          'text-overflow-ellipsis',
          'text-overflow-mode',
          'text-shadow',
          'text-transform',
          'text-wrap',
          '-webkit-text-size-adjust',
          '-ms-text-size-adjust',
          'letter-spacing',
          'word-break',
          'word-spacing',
          'word-wrap',
          'overflow-wrap',
          'tab-size',
          'white-space',
          'vertical-align',
          'list-style',
          'list-style-position',
          'list-style-type',
          'list-style-image',
        ],
      },
      {
        // 可访问性和互动性
        properties: [
          'pointer-events',
          '-ms-touch-action',
          'touch-action',
          'cursor',
          'visibility',
          'zoom',
          'table-layout',
          'empty-cells',
          'caption-side',
          'border-spacing',
          'border-collapse',
          'content',
          'quotes',
          'counter-reset',
          'counter-increment',
          'resize',
          'user-select',
          'nav-index',
          'nav-up',
          'nav-right',
          'nav-down',
          'nav-left',
        ],
      },
      {
        // svg样式
        properties: [
          'alignment-baseline',
          'baseline-shift',
          'dominant-baseline',
          'text-anchor',
          'word-spacing',
          'writing-mode',

          'fill',
          'fill-opacity',
          'fill-rule',
          'stroke',
          'stroke-dasharray',
          'stroke-dashoffset',
          'stroke-linecap',
          'stroke-linejoin',
          'stroke-miterlimit',
          'stroke-opacity',
          'stroke-width',

          'color-interpolation',
          'color-interpolation-filters',
          'color-profile',
          'color-rendering',
          'flood-color',
          'flood-opacity',
          'image-rendering',
          'lighting-color',
          'marker-start',
          'marker-mid',
          'marker-end',
          'mask',
          'shape-rendering',
          'stop-color',
          'stop-opacity',
        ],
      },
      {
        // 动画和过渡
        properties: [
          'transition',
          'transition-delay',
          'transition-timing-function',
          'transition-duration',
          'transition-property',
          'transform',
          'transform-origin',
          'animation',
          'animation-name',
          'animation-duration',
          'animation-play-state',
          'animation-timing-function',
          'animation-delay',
          'animation-iteration-count',
          'animation-direction',
        ],
      },
    ],
  },
};

Javascript

  1. 使用两个空格缩进,一行最好不超过 80 个字符。
  2. 使用单引号表示字符串,如果有变量应使用字符串模板而不是连接符。
  3. 普通变量命名应该小写驼峰形式;类、工厂函数都应该用大写驼峰,常量应全大写+下滑线分隔单词。
  4. 枚举类应该以Enum开头大写驼峰,接口应该以I开头大写驼峰
  5. 优先使用 es6 中语法格式,即对象内可以简写 function,尽量使用 const 然后再是 let 等。
  6. 语句末尾必须带分号。
  7. 条件语句内块级作用域必须带{}。
  8. 对象应静态化,即声明时就声明所有属性,如果需要后期添加属性应使用合并(Object.assign)。
  9. 尽量使用全等(===)。
  10. 对象、数组属性换行后最后一项需要加逗号
tips: 通用js的eslint
module.exports = {
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  extends: ['eslint:recommended'],
  plugins: ['html', 'flowtype'],
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
      legacyDecorators: true,
    },
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', // 禁止console
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // 禁止debugger
    'no-unused-vars': 'warn', // 禁止定义未使用的变量
    // 'no-extra-parens': [
    //   'warn',
    //   'all',
    //   { enforceForArrowConditionals: false, ignoreJSX: 'multi-line' },
    // ], // 禁止不必要的括号
    'no-template-curly-in-string': 'warn', // 禁止在常规字符串中出现模板字面量占位符语法
    'array-callback-return': 'error', // 强制数组方法的回调函数中有 return 语句
    'block-scoped-var': 'error', // 强制把变量的使用限制在其定义的作用域范围内
    curly: ['error', 'all'], // 强制所有控制语句使用一致的括号风格
    'default-case': 'error', // 要求 switch 语句中有 default 分支
    'dot-location': ['error', 'property'], // 强制在点号之前和之后一致的换行
    'dot-notation': 'warn', // 强制尽可能地使用点号
    'no-caller': 'error', // 禁用 arguments.caller 或 arguments.callee
    'no-eq-null': 'error', // 禁止在没有类型检查操作符的情况下与 null 进行比较
    'no-empty': 'warn', // 禁止空块语句
    'no-eval': 'error', // 禁用 eval()
    'no-extend-native': 'warn', // 禁止扩展原生类型
    'no-extra-bind': 'error', // 禁止无意义bind
    'no-floating-decimal': 'error', // 禁止数字字面量中使用前导和末尾小数点
    'no-implied-eval': 'error', // 禁止使用类似 eval() 的方法
    // 'no-magic-numbers': ['warn', { ignoreArrayIndexes: true }], // 禁止魔术数
    'no-multi-spaces': 'warn', // 禁止使用多个空格
    'no-multi-str': 'error', // 禁止使用多行字符串
    'no-self-compare': 'error', // 禁止自身比较
    'no-useless-call': 'error', // 禁止不必要的 .call() 和 .apply()
    'no-unused-expressions': ['error', { allowShortCircuit: true }], // 禁止出现未使用过的表达式
    'no-unmodified-loop-condition': 'error', // 禁用一成不变的循环条件
    'no-sequences': 'error', // 禁用逗号操作符
    'no-useless-return': 'error', // 禁止多余的 return 语句
    'require-await': 'error', // 禁止使用不带 await 表达式的 async 函数
    // 'no-undefined': 'error', // 禁止将 undefined 作为标识符
    'no-use-before-define': ['error', { classes: false, functions: false }], // 禁止在变量定义之前使用它们
    'no-const-assign': 'error', // 禁止修改 const 声明的变量
    'no-dupe-class-members': 'error', // 禁止类成员中出现重复的名称
    'no-duplicate-imports': 'error', // 禁止重复导入
    'no-var': 'error', // 禁止使用var
    'no-useless-escape': 'off', // 无意义的转意

    // 样式类
    'brace-style': ['warn', '1tbs', { allowSingleLine: true }], // 大括号风格要求
    'block-spacing': ['warn', 'always'], // 强制在代码块中开括号前和闭括号后有空格
    camelcase: ['warn', { properties: 'never', ignoreDestructuring: true }], //强制使用骆驼拼写法命名约定
    'comma-dangle': [
      'warn',
      {
        arrays: 'always-multiline',
        objects: 'always-multiline',
        imports: 'always-multiline',
        exports: 'always-multiline',
        functions: 'never',
      },
    ], // 要求使用拖尾逗号
    'comma-spacing': ['warn', { before: false, after: true }], // 强制在逗号周围使用空格
    'eol-last': ['warn', 'always'], // 要求文件末尾保留一行空行
    'func-call-spacing': ['warn', 'never'], // 要求在函数标识符和其调用之间有空格
    indent: ['warn', 2, { SwitchCase: 1 }], // 强制使用一致的缩进
    'jsx-quotes': ['warn', 'prefer-double'], // 强制在 JSX 属性中使用一致的双引号
    'new-cap': ['warn', { capIsNew: false }], // 要求构造函数首字母大写
    'no-multi-assign': 'warn', // 禁止连续赋值
    'no-multiple-empty-lines': ['warn', { max: 2 }], // 不允许多个空行
    'no-unneeded-ternary': 'warn', // 禁止可以表达为更简单结构的三元操作符
    'no-whitespace-before-property': 'warn', // 禁止属性前有空白
    'quote-props': ['warn', 'as-needed'], // 要求对象字面量属性名称使用引号
    quotes: ['warn', 'single'], // 强制使用一致的单引号
    semi: ['warn', 'always'], // 使用分号代替 ASI
    'arrow-body-style': ['warn', 'as-needed'], // 要求箭头函数体使用大括号
    'arrow-parens': ['warn', 'as-needed'], // 要求箭头函数的参数使用圆括号
    'arrow-spacing': ['warn', { before: true, after: true }], // 要求箭头函数的箭头之前或之后有空格
    'no-confusing-arrow': ['warn', { allowParens: true }], // 禁止在可能与比较操作符相混淆的地方使用箭头函数
    'prefer-const': 'warn', // 求使用 const 声明那些声明后不再被修改的变量
    'symbol-description': 'warn', // 要求 symbol 描述
    'space-before-function-paren': [
      'error',
      { anonymous: 'ignore', named: 'ignore' },
    ], // 要求剪头函数前需要空格
    'new-parens': 'warn', // 要求new一定带函数符
  },
};

Vue

vue 风格指南

  1. 减少使用 directive 和 mixin。
  2. v-for 指令下 key 尽量不使用 index。
  3. vue-html 属性应为小写+横杠连字符,props 声明内为小写驼峰,emit 的事件名也须为小写+横杠。
  4. vue-html 内不应该出现函数调用。
  5. 声明的函数功能应该有顺序性,即读取接口数据处理事件绑定
  6. 不要在 vue-html 内写过多逻辑或者内联样式。
tips: vue项目的eslint
module.exports = {
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: 'babel-eslint',
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
      legacyDecorators: true,
    },
  },
  extends: ['plugin:vue/essential', './eslintrc.default.js'],
  plugins: ['vue'],
  rules: {
    'vue/attribute-hyphenation': 'warn', // vue属性使用连字符
    'vue/html-closing-bracket-newline': 'warn', // vue-html标签闭合符换行
    'vue/html-end-tags': 'error', // vue标签强制闭合
    'vue/no-multi-spaces': 'warn', // 不允许多个空格
    'vue/name-property-casing': ['warn', 'PascalCase'], // vue组件名称强制驼峰
    'vue/no-spaces-around-equal-signs-in-attribute': 'warn', // 属性值前后不能有空格
    'vue/html-quotes': ['warn', 'double', { avoidEscape: false }], // html属性强制双引号
    'vue/v-on-style': ['warn', 'shorthand'], // v-on推荐简写
    'vue/v-bind-style': ['error', 'shorthand'], // v-bind推荐简写
    'vue/order-in-components': [
      'warn',
      {
        order: [
          'el',
          'name',
          'parent',
          'functional',
          ['delimiters', 'comments'],
          ['components', 'directives', 'filters'],
          'extends',
          'mixins',
          'inheritAttrs',
          'model',
          ['props', 'propsData'],
          'data',
          'computed',
          'watch',
          'LIFECYCLE_HOOKS',
          'methods',
          ['template', 'render'],
          'renderError',
        ],
      },
    ], // vue组件强制属性顺序
    'vue/no-boolean-default': ['warn', 'default-false'], // 布尔值props必须是否的默认值
    'vue/padding-line-between-blocks': 'warn', // vue文件两个块直接保持空格
    'vue/no-reserved-component-names': 'error', // 禁止使用保留名作为组件名
    'vue/no-parsing-error': [
      'warn',
      {
        'invalid-first-character-of-tag-name': false,
      },
    ], // 模板解析错误
  },
};

React

  1. 类组件方法可以用箭头函数定义,但是在通用组件内应尽量使用 construtor 内 bind 改变 this 指向的写法。
  2. 不允许在 setState 外更改 state,包括其引用。
  3. 应该在 componentDidMount 或 componentDidUpdate 时机读取接口。
  4. componentDidUpdate 如果涉及视图更新,应注意添加条件判断。
  5. 循环渲染的 dom 需要加 key,尽量不使用 index。
  6. 声明的函数功能应该有顺序性,即读取接口数据处理事件绑定
tips: react项目的eslint
module.exports = {
  root: true,
  env: {
    browser: true,
    commonjs: true,
    es6: true,
    jest: true,
    node: true,
  },
  parser: 'babel-eslint',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
    },
  },
  settings: {
    react: {
      version: 'detect',
    },
  },
  globals: {
    __DEV__: false,
  },
  extends: ['./eslintrc.default.js'],
  plugins: ['import', 'react', 'jsx-a11y'],
  rules: {
    indent: ['off', 2, { SwitchCase: 1 }], // 强制使用一致的缩进

    'react/no-access-state-in-setstate': 'warn', // setState中不能用state
    'react/no-adjacent-inline-elements': 'warn', // 防止相邻的内联元素不被空格分隔
    'react/no-children-prop': 'warn', // 不使用children属性
    'react/no-deprecated': 'warn', // 禁止使用不推荐的方法
    'react/no-will-update-set-state': 'error', // 禁止在componentWillUpdate中setState
    'react/no-direct-mutation-state': 'error', // 禁止修改state
    'react/no-is-mounted': 'error', // 不允许使用isMount
    'react/no-multi-comp': 'warn', // 单文件只能有一个组件
    'react/no-redundant-should-component-update': 'warn', // 禁止在PureComponent中使用shouldComponentUpdate
    'react/no-string-refs': 'error', // 不允许使用字符串ref
    'react/no-this-in-sfc': 'error', // 禁止在无状态组件中使用this
    'react/no-typos': 'error', // 防止属性输入打错字
    'react/no-unescaped-entities': 'warn', // 防止出现无效字符
    'react/no-unknown-property': 'warn', // 防止使用未知的DOM属性
    'react/prefer-es6-class': 'warn', // 使用es6类定义组件
    'react/require-render-return': 'error', // render需要返回
    'react/sort-comp': [
      'warn',
      {
        order: ['static-methods', 'lifecycle', 'everything-else', 'render'],
        groups: {
          lifecycle: [
            'displayName',
            'propTypes',
            'contextTypes',
            'childContextTypes',
            'mixins',
            'statics',
            'defaultProps',
            'state',
            'constructor',
            'getDefaultProps',
            'getInitialState',
            'getChildContext',
            'getDerivedStateFromProps',
            'componentWillMount',
            'UNSAFE_componentWillMount',
            'componentDidMount',
            'componentWillReceiveProps',
            'UNSAFE_componentWillReceiveProps',
            'shouldComponentUpdate',
            'componentWillUpdate',
            'UNSAFE_componentWillUpdate',
            'getSnapshotBeforeUpdate',
            'componentDidUpdate',
            'componentDidCatch',
            'componentWillUnmount',
          ],
        },
      },
    ], // 规定组件属性顺序
    'react/style-prop-object': 'warn', // 要求样式为对象
    'react/void-dom-elements-no-children': 'warn', // 防止空元素传children
    'react/jsx-boolean-value': 'warn', // 布尔值尽可能不传true
    'react/jsx-key': 'warn', // 循环dom需要设置key
    // 'react/jsx-no-bind': [
    //   'warn',
    //   { ignoreDOMComponents: true, ignoreRefs: true },
    // ], // 阻止jsx中编写函数
    'react/jsx-no-duplicate-props': 'warn', // jsx不允许相同属性
    'react/jsx-no-script-url': 'warn', // jsx不允许a扁鹊的javascript:
    'react/jsx-no-undef': 'error', // 不允许未定义就使用的组件
    'react/jsx-no-useless-fragment': 'warn', // 阻止无意义的fragment
    'react/jsx-pascal-case': 'warn', // 组件必须驼峰
    'react/jsx-props-no-multi-spaces': 'warn', // 属性名间不允许过多空格
    'react/jsx-uses-react': 'warn', // jsx需要React变量
    'react/jsx-uses-vars': 'error', // jsx引用变量前定义
  },
};

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!