前端代码规范
本文最后更新于:1 年前
写在前面
很多人可能会疑惑,代码自己写的看的舒服不就得了,为什么一定要这样。是的,当你是一个人维护的时候确实是这样子,但是当多人协同开发的时候,代码规范就尤为重要,不仅仅会减少因为格式、风格不一致的代码合并记录,还能减少容易出现的 bug 和出问题的概率,更能提升自己的编码水平;当你能完全掌控自己代码的时候就会发现,有规范有约定的代码是如何优雅和易调试。所以即使是单人维护开发的项目,也需要有代码规范约定,这种约定可能来自于业界的共识,也可能来自公司的规则,也可能是个人的理解。
虽然代码规范很多地方并不能用脚本或者人为强制控制,但是也希望各位能严格遵从,有问题可以提出并讨论,但是有规范的团队才是有战斗力的。保持自律,做个牛批的 programmer
代码规范
代码一致性和最佳实践。通过代码风格的一致性,降低维护代码的成本以及改善多人协作的效率。同时遵守最佳实践,确保页面性能得到最佳优化和高效的代码。
关于项目
- 项目文件命名应该全小写,以横杠隔开单词的形式,因为部分文件管理器不区分大小写
- 文件命名应该清晰易懂,最好包含该模块的功能和属性
- 如果一个模块文件夹内包含多个子模块,那默认导出模块文件应取名
index
HTML
原则:html 为前端的结构层,不应将样式层和逻辑层混写进入,减少没必要的多余空格,页面结构尽可能简单明了
。
- 缩进使用 2 个空格。
- 所有标签都必须闭合,包括一些自闭合的标签。
- 使用双引号而不是单引号。
- 一行应最好不超过 80 个字符,如过长可以属性换行。
- 属性过多的(2 个或以上)可换行。
<input
class="name-input"
id="name-input"
type="text"
placeholder="请输入名字"
maxlength="10"
/>
- 注释可以为大型模块标注结构起始。
<!--卡片头部-->
<div class="card-header">
<!--...-->
<!--大量结构-->
<!--...-->
</div>
<!--卡片头部 END-->
- 书写代码前应考虑结构复用性。
通用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
- 类名使用小写字母,用横杠隔开单词。
- 按结构层和样式层分层的原则来说,应避免出现 html 标签选择器。
- 选择器应尽量简单,减少复杂嵌套。
- 省略 0 属性值后面的单位。
- 注释格式
/* 这儿是注释 */
。 - 样式应该有一定顺序性,总结来说就是盒子属性、盒子样式、行内属性、行内样式、动画。
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
- 使用两个空格缩进,一行最好不超过 80 个字符。
- 使用单引号表示字符串,如果有变量应使用字符串模板而不是连接符。
- 普通变量命名应该
小写驼峰
形式;类、工厂函数都应该用大写驼峰
,常量应全大写+下滑线分隔单词。 - 枚举类应该以
Enum
开头大写驼峰
,接口应该以I
开头大写驼峰
。 - 优先使用 es6 中语法格式,即对象内可以简写 function,尽量使用 const 然后再是 let 等。
- 语句末尾必须带分号。
- 条件语句内块级作用域必须带{}。
- 对象应静态化,即声明时就声明所有属性,如果需要后期添加属性应使用合并(Object.assign)。
- 尽量使用全等(===)。
- 对象、数组属性换行后最后一项需要加逗号
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
- 减少使用 directive 和 mixin。
- v-for 指令下 key 尽量不使用 index。
- vue-html 属性应为小写+横杠连字符,props 声明内为小写驼峰,emit 的事件名也须为小写+横杠。
- vue-html 内不应该出现函数调用。
- 声明的函数功能应该有顺序性,即
读取接口
、数据处理
、事件绑定
。 - 不要在 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
- 类组件方法可以用箭头函数定义,但是在通用组件内应尽量使用 construtor 内 bind 改变 this 指向的写法。
- 不允许在 setState 外更改 state,包括其引用。
- 应该在 componentDidMount 或 componentDidUpdate 时机读取接口。
- componentDidUpdate 如果涉及视图更新,应注意添加条件判断。
- 循环渲染的 dom 需要加 key,尽量不使用 index。
- 声明的函数功能应该有顺序性,即
读取接口
、数据处理
、事件绑定
。
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 协议 ,转载请注明出处!