前端代码规范

Vue官方编码规范

https://v3.cn.vuejs.org/style-guide/#规则类别

代码规范

  • 代码注释:好的注释习惯可以极大的减少沟通成本,不一定需要每一行都需要注释,但是在一些关键节点,根据我们的开发经验来说的话,有注释或者说有一个好的注释是很关键的。
  • 方法拆分:一个function,通常情况下超过了50行就需要考虑一下是否需要都冗余在一个方法内,是不是需要拆分程多个function。如果看到一个方法超过30行,基本上去理解阅读的难度就会提升。
  • 组件拆分:一个vue文件(不包含css)一般不会超过500行,哪怕vue的模版代码较多,也不可能完全融合在一起,需要根据UI或者原型图进行更粒度的拆分。
  • 预防性编码:我们拿到一个复杂的,嵌套层数较多的对象的时候(通常会是接口给的),我们需要考虑在某一个字段不存在的情况,比如userInfos.payInfo.lastTime.这种多层解构的情况,我们需要考虑如果payInfo如果为null,那前端页面就崩了。所以userInfos.payInfo&&userInfos.payInfo.lastTime或者userInfos.payInfo?.lastTime 这样写就可以避免
  • 工具统一:eslint+prettier,所有的前端项目保持统一,需要配置ws或者vscode配置,保持当前项目的标准按照项目内的clirc文件而不是按照本机全局的配置。在未经过大家同意的情况下 不得擅自改配置文件

条件判断一般都需要注释(通常情况下 业务环境的条件判断都不会这么简单)

// 当acc等于xxx的时候返回 ,否则返回
if(acc===TheConfig.isAcc){
	acc = xxx
}else{
	acc = yyy
}

// 当acc等于xxx的时候
if(acc===TheConfig.isAcc){
	acc = a   //赋值xxx
}else if(
	 acc = b  //赋值xxx
)else{
	acc = c  //赋值xxx
}

switch(type){
		// 当type = a的时候 xxx
	case 'a':
		return 'xxx';

		// 当type = b的时候 xxx
	case 'b':
		return 'xxx';

		// 当type = c的时候 xxx
	case 'c':
		return 'xxx';
}

反面教材

这种业务场景已经算比较复杂了switch+if else。然而我要读懂的话 我得一个类型一个类型的去找。浪费时间,增加沟通成本。

switch (type) {
        case 'shop':
          sensors.track('Lengure_SubClick', {page_name: '极氪联盟首页', sub_name: '盟友地图'})
          if (platform === 'Android') {
            url = 'zeekr://app.zeekrlife.com/community/zeekrUnionListPage?title=盟友地图'
            window.location.href = url
          } else {
            url = '/community/zeekrUnionListPage?title=盟友地图'
            bridge.callApp({type: 'navigate', data: {route: url}})
          }
          // 因为已经用了brige,所以是在app内
          break
        case 'punch':
          sensors.track('Lengure_SubClick', {page_name: '极氪联盟首页', sub_name: '我要打卡'})
          if (platform === 'Android') {
            url = 'zeekr://app.zeekrlife.com/community/zeekrUnionListPage?title=我要打卡&needCurrentCity=true'
            window.location.href = url
          } else {
            url = '/community/zeekrUnionListPage?title=我要打卡&needCurrentCity=true'
            bridge.callApp({type: 'navigate', data: {route: url}})
          }
          break
        case 'collect':
          sensors.track('Lengure_SubClick', {page_name: '极氪联盟首页', sub_name: '我的收藏'})
          if (platform === 'Android') {
            url = 'zeekr://app.zeekrlife.com/community/userCollectPage?selectIndex=1'
            window.location.href = url
          } else {
            url = '/community/userCollectPage?selectIndex=1'
            bridge.callApp({type: 'navigate', data: {route: url}})
          }
          break
        default:
          break
      }
async function queryWrap() {
  queryObj.data.leagueStoreId = storeId
  const res = await queryArticle(queryObj.data,{showLoading: false})
  if (queryObj.data.pageNo === 1) {
    articleLoad.value = true
  }
  for (let i = 0; i < res.data.list.length; i++) {
    let item = res.data.list[i]
    if (item.imgList.length >= 4) {
      item.totalImgNum = item.imgList.length
      item.imgList = item.imgList.slice(0, 4)
    }
  }
  dataList.value = [...dataList.value, ...res.data.list]
  queryObj.data.pageNo++
  if (res.data.list.length < queryObj.data.pageSize) {
    queryObj.isFinish = true
  }
}

复杂的方法或者通用型的工具类需要注释

参数需要进行注释或者当前这个方法是干嘛用的需要注释

// fileName: xxx
// isAsync: 是否需要同步
// onError: 报错回调
// 返回:当前处理后的结果
function fileException(fileName,isAsnyc,onError){
	const content = fs.readfile(path.resolve(fileName))
	content.replace(/[a-z](.*)$/,'xxx')
	fs.writefile(path.resolve(xx),()=>{
		onError(aaa)
	})

	return vvv
}

/**
* filename xxx
* level : xxx
*/
class fileContext{
	constructor(filename,level){
		this.filename = filename
		this.level = level
	}
}

props或者data 如果有注释更好

data(){
	return {
		xx:false // 干嘛用
		yy:false // 干嘛用
		zz:false // 干嘛用
		gg:false // 干嘛用
	}
}

const mixHeight = useRef('') // 干嘛用

props:{
	name:{  // 用户名称
		
	}
}

三元运算符需要注释(!!)

通常三元或者多元运算符都很难一眼看明白到底是干嘛用的有注释会很方便

// 当xxx的时候xxx
isMore?xxx:isDetail?xx:yy

eslint配置

配置是基于@eslint-plugin-vue。eslint可以强规范一些代码细节,配合prettier可以很方便实现规范统一。

module.exports = {
	env: {
		node: true, // 保证js文件 支持commonjs 支持node环境
		browser: true, // 支持浏览器环境
		es6: true, // 校验es6
	},
	parser: 'vue-eslint-parser',
	extends: [
		'eslint:recommended',  //eslint-plugin-vue提供的基础eslint规则
		'plugin:vue/vue3-recommended',  // eslint-plugin-vue针对vue3的基础规则
		'prettier', // eslint和prettier配置项冲突禁用 要确保放在所有扩展项的最后 这样才能正确的覆盖前面可能有冲突的规则
		// 'plugin:vue/recommended' // eslint-plugin-vue 针对vue2的基础规则
	],
	rules: {
		// override/add rules settings here, such as:
		// 'vue/no-unused-vars': 'error'
	},
}

需要涉及的安装包:

  • eslint:eslint基础核心包。
  • vue-eslint-parser:针对vue3如果是
  • eslint-config-prettier:针对prettier的配置项和eslint配置项冲突,禁掉冲突的插件规则,保持插件功能单一,eslint针对代码格式校验,prettier只对代码格式化处理。
  • @vue/cli-plugin-eslint:vue cli默认安装插件 如果开启lint的话,支持在编译期间不报错,只在ide报错 提示开发更正有问题的code.

配置eslint遇到的一些问题:

  • 上面四个包都需要安装,并且都应该安装到dev环境,不然eslint跑起来如果配置的config的包不在一个环境,会报错,没办法去校验对应的文件。
  • eslint只有在文件保存的时候才飘红,按照正常的逻辑应该是修改代码的时候就应该及时提示,这个最后需要在vscode的setting文件中修改配置 “eslint.run”: “onType”
  • js文件中required或者直接用全局变量 __dirname等 会报错,因为eslintrc.js没有配置环境,env应该增加node:true 注:eslint配置解释
module.exports = {
  root: true, // 当前配置为根配置,将不再从上级文件夹查找配置
  parserOptions: {
    parser: 'babel-eslint', // 采用 babel-eslint 作为语法解析器
    sourceType: 'module'  // 指定来源的类型,有两种script或module
  },
  env: {
    browser: true, // 设置为所需检查的代码是在浏览器环境运行的
    node: true, // 设置为所需检查的代码是nodejs环境运行的
    es6: true // 设置所需检查代码为es6语法书写
  },
  // 配置js全局变量,因为是uni-app,全局的uni是不需要引入的
  globals: {
    uni: 'readonly',
    wx: 'readonly',
  },
  extends: ['plugin:vue/recommended', 'eslint:recommended', 'plugin:prettier/recommended'], // 扩展使用 vue 检查规则和eslint推荐规则
  rules: {
    'prettier/prettier': 'error',
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', // 只有开发环境可以使用console
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // 只有开发环境可以使用debugger
    'vue/attribute-hyphenation': 0, // 忽略属性连字
    'vue/max-attributes-per-line':[2, { singleline: 10, multiline: { max: 1, allowFirstLine: false } }], // 每行最大属性
    'vue/singleline-html-element-content-newline': 'off', // 单行html元素内容在新的一行
    'vue/multiline-html-element-content-newline': 'off', // 多行html元素内容在新的一行
    'vue/html-closing-bracket-newline': 'off', // html右括号在新的一行
    'vue/no-v-html': 'off', // 不使用v-html
    'vue/html-self-closing': 0, // 忽略html标签自闭合
    'accessor-pairs': 2, // 应同时设置setter和getter
    'arrow-spacing': [2, { before: true, after: true }], // 箭头间距
    'vue/require-default-prop': 0, // 不检查默认属性
    'vue/require-prop-types': 0, // 不检查默认类型
    'block-spacing': [2, 'always'], // 块间距
    'brace-style': [2, '1tbs', { allowSingleLine: true }], // 大括号样式允许单行
    'camelcase': [2, { properties: 'always' }], // 为属性强制执行驼峰命名
    'comma-dangle': [2, 'never'], // 逗号不使用悬挂
    'comma-spacing': [2, { before: false, after: true }], // 逗号间距
    'comma-style': [2, 'last'], //(默认)与数组元素,对象属性或变量声明在同一行之后和同一行需要逗号
    'constructor-super': 2,
    'consistent-this': [2, 'that'], // 强制this别名为that
    'curly': [2, 'multi-line'], // 当一个块只包含一条语句时,不允许省略花括号。
    'dot-location': [2, 'property'], //成员表达式中的点应与属性部分位于同一行
    'eol-last': 2, // 强制文件以换行符结束(LF)
    'eqeqeq': ['error', 'always', { null: 'ignore' }], // 强制使用全等
    'generator-star-spacing': [2, { before: true, after: true }], // 生成器中'*'两侧都要有间距
    'global-require': 1, // 所有调用require()都位于模块的顶层
    'indent': [2, 2, { SwitchCase: 2 }], // 缩进风格
    'key-spacing': [2, { beforeColon: false, afterColon: true }], // 强制在对象字面量属性中的键和值之间保持一致的间距
    'keyword-spacing': [2, { before: true, after: true }], // 关键字如if/function等的间距
    'new-cap': [2, { newIsCap: true, capIsNew: false }],// 允许在没有new操作符的情况下调用大写启动的函数;(默认)要求new使用大写启动函数调用所有操作符
    'new-parens': 2, // JavaScript通过new关键字调用函数时允许省略括号
    'no-array-constructor': 1, // 不允许使用Array构造函数。除非要指定生成数组的长度
    'no-class-assign': 2, // 不允许修改类声明的变量
    'no-const-assign': 2, // 不能修改使用const关键字声明的变量
    'no-control-regex': 0, // 不允许正则表达式中的控制字符
    'no-delete-var': 2, // 不允许在变量上使用delete操作符
    'no-dupe-args': 2, // 不允许在函数声明或表达式中使用重复的参数名称
    'no-dupe-class-members': 2, // 不允许在类成员中使用重复的参数名称
    'no-dupe-keys': 2, // 不允许在对象文字中使用重复键
    'no-duplicate-case': 2, // 不允许在switch语句的case子句中使用重复的测试表达式
    'no-empty-character-class': 2, // 不允许在正则表达式中使用空字符类
    'no-empty-pattern': 2, // 不允许空块语句
    'no-eval': 2, // 不允许使用eval
    'no-ex-assign': 2, // 不允许在catch子句中重新分配例外
    'no-extend-native': 2, // 不允许直接修改内建对象的原型
    'no-extra-boolean-cast': 2, // 禁止不必要的布尔转换
    'no-extra-parens': [2, 'functions'], // 仅在函数表达式附近禁止不必要的括号
    'no-fallthrough': 2, //消除一个案件无意中掉到另一个案件
    'no-floating-decimal': 2, //消除浮点小数点,并在数值有小数点但在其之前或之后缺少数字时发出警告
    'no-func-assign': 2, // 允许重新分配function声明
    'no-implied-eval': 2, // 消除隐含eval()
    'no-inner-declarations': [2, 'functions'], // 不允许function嵌套块中的声明
    'no-invalid-regexp': 2, // 不允许RegExp构造函数中的无效正则表达式字符串
    'no-irregular-whitespace': 2, // 捕获无效的空格
    'no-iterator': 2, // 消除阴影变量声明
    'no-label-var': 2, // 禁止创建与范围内的变量共享名称的标签
    'no-labels': [2, { allowLoop: false, allowSwitch: false }], // 消除 JavaScript 中使用带标签的语句
    'no-lone-blocks': 2, // 消除脚本顶层或其他块中不必要的和可能混淆的块
    'no-mixed-spaces-and-tabs': 2, // 不允许使用混合空格和制表符进行缩进
    'no-multi-spaces': 2, // 禁止在逻辑表达式,条件表达式,声明,数组元素,对象属性,序列和函数参数周围使用多个空格
    'no-multi-str': 2, // 防止使用多行字符串
    'no-multiple-empty-lines': [2, { max: 1 }], // 最多一个空行
    'no-native-reassign': 2, // 不允许修改只读全局变量
    'no-new-object': 2, // 不允许使用Object构造函数
    'no-new-require': 2, // 消除new require表达的使用
    'no-new-symbol': 2, // 防止Symbol与new 同时使用的意外错误
    'no-new-wrappers': 2, // 杜绝使用String,Number以及Boolean与new操作
    'no-obj-calls': 2, // 不允许调用Math,JSON和Reflect对象作为功能
    'no-octal': 2, // 不允许使用八进制文字
    'no-octal-escape': 2, // 不允许字符串文字中的八进制转义序列
    'no-path-concat': 2, // 防止 Node.js 中的目录路径字符串连接无效
    'no-redeclare': 2, // 消除在同一范围内具有多个声明的变量
    'no-regex-spaces': 2, // 在正则表达式文字中不允许有多个空格
    'no-return-assign': [2, 'except-parens'], // 消除return陈述中的任务,除非用括号括起来
    'no-self-assign': 2, // 消除自我分配
    'no-self-compare': 2, // 禁止比较变量与自身
    'no-sequences': 2, // 禁止使用逗号运算符
    'no-sparse-arrays': 2, // 不允许稀疏数组文字
    'no-this-before-super': 2, // 在呼call前标记this/super关键字super()
    'no-throw-literal': 2, // 不允许抛出不可能是Error对象的文字和其他表达式
    'no-trailing-spaces': 2, // 不允许在行尾添加尾随空白
    'no-undef': 2, // 任何对未声明的变量的引用都会导致错误
    'no-undef-init': 2, // 消除初始化为undefined的变量声明
    'no-underscore-dangle': 2, // 标识符不能以_开头或结尾
    'no-unexpected-multiline': 2, // 不允许混淆多行表达式
    'no-unmodified-loop-condition': 2, // 查找循环条件内的引用,然后检查这些引用的变量是否在循环中被修改
    'no-unneeded-ternary': [2, { defaultAssignment: false }], // 不允许将条件表达式作为默认的分配模式
    'no-unreachable': 2, // 不允许可达代码return,throw,continue,和break语句后面还有语句。
    'no-unsafe-finally': 2, // 不允许return,throw,break,和continue里面的语句finally块
    'no-unused-vars': [2, { vars: 'all', args: 'after-used' }],
    // 消除未使用的变量,函数和函数的参数
    // vars: 'all' 检查所有变量的使用情况,包括全局范围内的变量。这是默认设置。 args: 'after-used' 只有最后一个参数必须使用。例如,这允许您为函数使用两个命名参数,并且只要您使用第二个参数,ESLint 就不会警告您第一个参数。这是默认设置。
    'no-useless-call': 2, // 标记使用情况,Function.prototype.call()并且Function.prototype.apply()可以用正常的函数调用来替代
    'no-useless-computed-key': 2, // 禁止不必要地使用计算属性键
    'no-useless-constructor': 2, // 在不改变类的工作方式的情况下安全地移除的类构造函数
    'no-useless-escape': 0, // 禁用不必要的转义字符
    'no-whitespace-before-property': 2, // 如果对象的属性位于同一行上,不允许围绕点或在开头括号之前留出空白
    'no-with': 2, // 禁用with
    'no-var': 2, // 禁用var
    'one-var': [2, { initialized: 'never' }], // 强制将变量声明为每个函数(对于var)或块(对于let和const)范围一起声明或单独声明。 initialized: 'never' 每个作用域要求多个变量声明用于初始化变量
    'operator-linebreak': [2, 'after', { overrides: { '?': 'before', ':': 'before' } }], // 实施一致的换行
    'padded-blocks': [2, 'never'], // 在块内强制执行一致的空行填充
    'prefer-destructuring': ['error', { object: false, array: false }], // 此规则强制使用解构而不是通过成员表达式访问属性。
    'quotes': [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], // avoidEscape: true 允许字符串使用单引号或双引号,只要字符串包含必须以其他方式转义的引号 ;allowTemplateLiterals: true 允许字符串使用反引号
    'radix': 2, // parseInt必须指定第二个参数
    'semi': [2, 'never'], // 不使用分号
    'semi-spacing': [2, { before: false, after: true }], // 强制分号间隔
    'space-before-blocks': [2, 'always'], // 块必须至少有一个先前的空间
    'space-before-function-paren': [2, 'never'], // 在(参数后面不允许任何空格
    'space-in-parens': [2, 'never'], // 禁止或要求(或)左边的一个或多个空格
    'space-infix-ops': 2, // 强制二元运算符左右各有一个空格
    'space-unary-ops': [2, { words: true, nonwords: false }], // words: true 如:new,delete,typeof,void,yield 左右必须有空格 // nonwords: false 一元运算符,如:-,+,--,++,!,!!左右不能有空格
    'spaced-comment': [2, 'always', { markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] }], // 注释开始后,此规则将强制间距的一致性//或/*
    'template-curly-spacing': [2, 'never'], // 不允许大括号内的空格
    'use-isnan': 2, // 禁止比较时使用NaN,只能用isNaN()
    'valid-typeof': 2, // 必须使用合法的typeof的值
    'wrap-iife': [2, 'any'], // 立即执行函数表达式的小括号风格
    'yield-star-spacing': [2, 'both'], // 强制执行*周围 yield*表达式的间距,两侧都必须有空格
    'yoda': [2, 'never'],
    'prefer-const': 2, // 使用let关键字声明的变量,但在初始分配后从未重新分配变量,应改为const声明
    'object-curly-spacing': [2, 'always', { objectsInObjects: false }], // 不允许以对象元素开始和/或以对象元素结尾的对象的大括号内的间距
    'array-bracket-spacing': [2, 'never'] // 不允许数组括号内的空格
  }
}

prettier配置

这个没什么好说的,vscode支持保存自动按照配置格式化,配合eslint可以自动修复有问题的代码。

配置如下:

module.exports = {
  printWidth: 80, //(默认值)单行代码超出 80 个字符自动换行
  tabWidth: 2, //(默认值)一个 tab 键缩进相当于 2 个空格
  useTabs: true, // 行缩进使用 tab 键代替空格
  semi: false, //(默认值)语句的末尾加上分号
  singleQuote: true, // 使用单引号
  quoteProps: "as-needed", //(默认值)仅仅当必须的时候才会加上双引号
  jsxSingleQuote: true, // 在 JSX 中使用单引号
  trailingComma: "all", // 不用在多行的逗号分隔的句法结构的最后一行的末尾加上逗号
  bracketSpacing: true, //(默认值)在括号和对象的文字之间加上一个空格
  jsxBracketSameLine: true, // 把 > 符号放在多行的 JSX 元素的最后一行
  arrowParens: "avoid", // 当箭头函数中只有一个参数的时候可以忽略括弧
  htmlWhitespaceSensitivity: "ignore", // vue template 中的结束标签结尾尖括号掉到了下一行
  vueIndentScriptAndStyle: false, //(默认值)对于 .vue 文件,不缩进 <script> 和 <style> 里的内容
  embeddedLanguageFormatting: "auto", //(默认值)允许自动格式化内嵌的代码块
};

关于一些eslint-config-xxx包

其实内部就是对rules进行集中编写,或者继承其他的eslint包,方便统一管理,所以后面我们会考虑按照vue3 vue2 或者 uniapp 进行单独的定制,生成对应的npm包,避免一个项目接入需要手动安装多个包,增加接入成本。