华夏出行前端组代码规范 | 华行前端协作规范文档

Posted by Joey on June 1, 2021

前端协作规范 华行前端协作规范文档(持续完善…) Author E-mail 乔宇乔宇 527324363@qq.com

项目架构版本

项目框架 UI框架 名称 vk-unicloud-router uView 版本 1.9.2 1.8.4

前端项目

前端目前开发的项目管理及介绍 摩范速运2.0管理系统(H5) 项目介绍: 仓库地址:传送门

工作流规范

版本规范 主版本号:大版本号 次版本号:功能性新增 修订号:问题修正

提交信息规范 commit message后缀加入开发者名称并用括号()括起来,保持风格式统一的提交信息有助于快速识别开发者和开发任务 版本库不只是存放代码的仓库, 它记录项目的开发日志, 它应该要清晰表达这次提交的做了什么. 这些记录应该可以帮助后来者快速地学习和回顾代码, 也应该方便其他协作者review你的代码

发布工作流规范 代码变更/编译 提交代码变更到远程版本库 创建合并请求并给其他负责人或同事进行Code review 检查无误合并代码到项目分支/检查有误拒绝合并打回修改并优化 线上问题紧急修复Hotfix分支修复,并以时间线命名。例:hotfix_20210101 线上问题紧急修复打tag 推送

技术栈规范 编程语言 - Typescript或Javascript 编辑器推荐VSCode,Uniapp项目请使用Hbuilder

HTML 一般情况下统一使用 “UTF-8” 编码

元素及标签闭合 HTML元素共有以下5种: 空元素:area、base、br、col、command、embed、hr、img、input、keygen、link、meta、param、source、track、wbr 原始文本元素:script、style RCDATA元素:textarea、title 外来元素:来自MathML命名空间和SVG命名空间的元素。 常规元素:其他HTML允许的元素都称为常规元素。 原始文本元素、RCDATA元素以及常规元素都有一个开始标签来表示开始,一个结束标签来表示结束。 某些元素的开始和结束标签是可以省略的,如果规定标签不能被省略,那么就绝对不能省略它。 空元素只有一个开始标签,且不能为空元素设置结束标签。 外来元素可以有一个开始标签和配对的结束标签,或者只有一个自闭合的开始标签,且后者情况下该元素不能有结束标签。 HTML标签名、类名、标签属性和大部分属性值统一用小写和驼峰命名法 推荐:

不推荐:

元素属性值使用双引号语法,不要用单引号 代码缩进统一使用四个空格进行代码缩进,使得各编辑器表现一致(各编辑器有相关配置) 纯数字输入框使用 type=”tel” 而不是 type=”number” 元素嵌套规范,每个块状元素独立一行,内联元素可选 单行注释,一般用于简单的描述,如某些状态描述、属性描述等,注释内容前后各一个空格字符,注释位于要注释代码的上面,单独占一行

...

CSS 样式书写统一使用展开式 .jdc{ display: block; width: 50px; } 样式选择器,属性名,属性值关键字全部使用小写字母 / 驼峰命名书写,属性字符串允许使用大小写。 每个属性声明末尾都要加分号; 左括号与类名之间一个空格,冒号与属性值之间一个空格 css属性值需要用到引号时,统一使用单引号 每一个component中最外层的Class命名与文件名称同步,多个单词为驼峰命名 注释内容第一个字符和最后一个字符都是一个空格字符,单独占一行,行与行之间相隔一行 注释内容第一个字符和最后一个字符都是一个空格字符,/* 与 模块信息描述占一行,多个横线分隔符-与/占一行,行与行之间相隔两行 / Module A —————————————————————- */ .mod_a {} 可复用属性尽量抽离为页面变量,易于统一维护 // CSS .jdc { color: red; border-color: red; }

// SCSS $color: red; .jdc { color: $color; border-color: $color; }

JS规范 原始类型: 存取原始类型直接作用于值本身const foo = 1let bar = foobar = 9console.log(foo, bar) // 1, 9 布尔类型 Null 类型 Undefined 类型 数字类型 BigInt 类型 字符串类型 符号类型 Symbol 复杂类型: 访问复杂类型作用于值的引用const foo = [1, 2, 3]const bar = foobar[0] = 9console.log(foo[0], bar[0]) // 9, 9 object array function 引用 请记得 const 和 let 都是块级作用域,var 是函数级作用域 // const and let only exist in the blocks they are defined in. { let a = 1 const b = 1 } console.log(a) // ReferenceError console.log(b) // ReferenceError

对所有引用都使用 const,不要使用 var,eslint: prefer-const, no-const-assign 原因:这样做可以确保你无法重新分配引用,以避免出现错误和难以理解的代码 // bad var a = 1 var b = 2

// good const a = 1 const b = 2

如果引用是可变动的,使用 let 代替 var,eslint: no-var // bad var count = 1 if (count < 10) { count += 1 }

// good let count = 1 if (count < 10) { count += 1 }

对象 请使用字面量值创建对象,eslint: no-new-object// badconst a = new Object{}// goodconst a = {} 当使用动态属性名创建对象时,请使用对象计算属性名来进行创建 原因:因为这样做就可以让你在一个地方定义所有的对象属性 function getKey(k) { return a key named ${k} }

// bad const obj = { id: 5, name: ‘San Francisco’ }; obj[getKey(‘enabled’)] = true

// good const obj = { id: 5, name: ‘San Francisco’, [getKey(‘enabled’)]: true };

将简写的对象属性分组后统一放到对象声明的开头 原因:这样更容易区分哪些属性用了简写的方式 const job = ‘FrontEnd’ const department = ‘JDC’

// bad const item = { sex: ‘male’, job, age: 25, department }

// good const item = { job, department, sex: ‘male’, age: 25 }

只对非法标识符的属性使用引号,eslint: quote-props 原因:因为通常来说我们认为这样主观上会更容易阅读,这样会带来代码高亮上的提升,同时也更容易被主流 JS 引擎优化 // bad const bad = { ‘foo’: 3, ‘bar’: 4, ‘data-blah’: 5 }

// good const good = { foo: 3, bar: 4, ‘data-blah’: 5 }

不要直接使用 Object.prototype 的方法, 例如 hasOwnProperty, propertyIsEnumerable 和 isPrototypeOf 方法,eslint: no-prototype-builtins

// bad console.log(object.hasOwnProperty(key))

// good console.log(Object.prototype.hasOwnProperty.call(object, key))

// best const has = Object.prototype.hasOwnProperty // cache the lookup once, in module scope. console.log(has.call(object, key)) /* or */ import has from ‘has’ // https://www.npmjs.com/package/has console.log(has(object, key))

优先使用对象展开运算符 … 来做对象浅拷贝而不是使用 Object.assign,使用对象剩余操作符来获得一个包含确定的剩余属性的新对象 // very bad const original = { a: 1, b: 2 } const copy = Object.assign(original, { c: 3 }) // this mutates original ಠ_ಠ delete copy.a // so does this

// bad const original = { a: 1, b: 2 } const copy = Object.assign({}, original, { c: 3 }) // copy => { a: 1, b: 2, c: 3 }

// good const original = { a: 1, b: 2 } const copy = { …original, c: 3 } // copy => { a: 1, b: 2, c: 3 }

const { a, …noA } = copy // noA => { b: 2, c: 3 }

数组 请使用字面量值创建数组 向数组中添加元素时,请使用 push 方法 使用展开运算符 … 复制数组 itemsCopy = […items] 把一个可迭代的对象转换为数组时,使用展开运算符 … 而不是 Array.from const foo = document.querySelectorAll(‘.foo’)

// good const nodes = Array.from(foo)

// best const nodes = […foo]

使用 Array.from 来将一个类数组对象转换为数组 const arrLike = { 0: ‘foo’, 1: ‘bar’, 2: ‘baz’, length: 3 }

// bad const arr = Array.prototype.slice.call(arrLike)

// good const arr = Array.from(arrLike)

遍历迭代器进行映射时使用 Array.from 代替扩展运算符 …, 因为这可以避免创建中间数组 // bad const baz = […foo].map(bar)

// good const baz = Array.from(foo, bar)

使用数组的 map 等方法时,请使用 return 声明,如果是单一声明语句的情况,可省略 return 如果一个数组有多行则要在数组的开括号后和闭括号前使用新行 // bad const arr = [ [0, 1], [2, 3], [4, 5] ]

const objectInArray = [{ id: 1 }, { id: 2 }]

const numberInArray = [ 1, 2 ]

// good const arr = [[0, 1], [2, 3], [4, 5]]

const objectInArray = [ { id: 1 }, { id: 2 } ]

const numberInArray = [ 1, 2 ]

解构赋值 当需要使用对象的多个属性时,请使用解构赋值,eslint: prefer-destructuring 愿意:解构可以避免创建属性的临时引用 // bad function getFullName (user) { const firstName = user.firstName const lastName = user.lastName

return ${firstName} ${lastName} }

// good function getFullName (user) { const { firstName, lastName } = user

return ${firstName} ${lastName} }

// better function getFullName ({ firstName, lastName }) { return ${firstName} ${lastName} }

当需要使用数组的多个值时,请同样使用解构赋值 函数需要回传多个值时,请使用对象的解构,而不是数组的解构 原因:可以非破坏性地随时增加或者改变属性顺序 // bad function doSomething () { return [top, right, bottom, left] }

// 如果是数组解构,那么在调用时就需要考虑数据的顺序 const [top, xx, xxx, left] = doSomething()

// good function doSomething () { return { top, right, bottom, left } }

// 此时不需要考虑数据的顺序 const { top, left } = doSomething()

字符串 字符串统一使用单引号的形式 ‘’ 程序化生成字符串时,请使用模板字符串 不要对字符串使用eval(),会导致太多漏洞 不要在字符串中使用不必要的转义字符 // bad const foo = ‘'this' \i\s "quoted"’

// good const foo = ‘'this' is “quoted”’ const foo = my name is '${name}'

函数 不要使用Function构造函数创建函数 // bad const add = new Function(‘a’, ‘b’, ‘return a + b’)

// still bad const subtract = Function(‘a’, ‘b’, ‘return a - b’)

在函数签名中使用空格 const f = function(){} const g = function (){} const h = function() {}

// good const x = function b () {} const y = function a () {}

将参数默认值放在最后 // bad function handleThings (opts = {}, name) { // … }

// good function handleThings (name, opts = {}) { // … }

不要更改参数 原因:操作作为参数传入的对象可能在原始调用中造成意想不到的变量副作用 // bad function f1 (obj) { obj.key = 1 }

// good function f2 (obj) { const key = Object.prototype.hasOwnProperty.call(obj, ‘key’) ? obj.key : 1 }

不要给参数重新赋值 // bad function f1 (a) { a = 1 }

function f2 (a) { if (!a) { a = 1 } }

// good function f3 (a) { const b = a || 1 }

function f4 (a = 1) { }

调用可变参数函数时建议使用展开运算符 … // bad const x = [1, 2, 3, 4, 5] console.log.apply(console, x)

// good const x = [1, 2, 3, 4, 5] console.log(…x)

// bad new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]))

// good new Date(…[2016, 8, 5])

箭头函数 当你必须使用函数表达式(传递匿名函数)时,使用箭头函数标记 如果您有一个相当复杂的函数,则可以将该逻辑移到其自己的命名函数表达式中 // bad [1, 2, 3].map(function (x) { const y = x + 1 return x * y })

// good [1, 2, 3].map((x) => { const y = x + 1 return x * y })

如果函数体只包含一条没有副作用的返回表达式的语句,可以省略花括号并使用隐式的 return, 否则保留花括号并使用 return 语句 // bad [1, 2, 3].map(number => { const nextNumber = number + 1 A string containing the ${nextNumber}. })

// good [1, 2, 3].map(number => A string containing the ${number}.)

// good [1, 2, 3].map((number) => { const nextNumber = number + 1 return A string containing the ${nextNumber}. })

// good [1, 2, 3].map((number, index) => ({ index: number }))

// No implicit return with side effects function foo(callback) { const val = callback() if (val === true) { // Do something if callback returns true } }

let bool = false

// bad foo(() => bool = true)

// good foo(() => { bool = true })

一旦表达式跨多行,使用圆括号包裹以便更好阅读 // bad [‘get’, ‘post’, ‘put’].map(httpMethod => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod ) )

// good [‘get’, ‘post’, ‘put’].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod ) ))

函数如果只接收一个参数并且没使用用花括号,则省略圆括号,否则为了清晰明确则使用圆括号包裹参数,注意:总是使用圆括号也是可以接受的,eslint 中的 “always” 选项,eslint: arrow-parens // bad [1, 2, 3].map((x) => x * x)

// good [1, 2, 3].map(x => x * x)

// good [1, 2, 3].map(number => ( A long string with the ${number}. It’s so long that we’ve broken it + ‘over multiple lines!’ ))

// bad [1, 2, 3].map(x => { const y = x + 1 return x * y })

// good [1, 2, 3].map((x) => { const y = x + 1 return x * y })

类&构造函数 使用 class,避免直接操作 prototype使用 extends 来实现继承 // bad const inherits = require(‘inherits’) function PeekableQueue(contents) { Queue.apply(this, contents) } inherits(PeekableQueue, Queue) PeekableQueue.prototype.peek = function () { return this.queue[0] }

// good class PeekableQueue extends Queue { peek () { return this.queue[0] } }

如果未声明构造函数,则类会有一个默认的构造函数,没必要用空的构造函数或者将其委托给父类,eslint: no-useless-constructor // bad class Jedi { constructor () {}

getName() { return this.name } }

// bad class Rey extends Jedi { constructor (…args) { super(…args) } }

// good class Rey extends Jedi { constructor (…args) { super(…args) this.name = ‘Rey’ } }

避免类成员重复,eslint: no-dupe-class-members 原因:重复的类成员声明会默认使用最后声明的,通常会导致 bug // bad class Foo { bar () { return 1 } bar () { return 2 } }

// good class Foo { bar () { return 1 } }

// good class Foo { bar () { return 2 } }

模块 使用标准的 ES6 模块语法 import 和 export 原因:模块是未来,让我们现在开始使用未来的特性 // bad const util = require(‘./util’) module.exports = util

// good import Util from ‘./util’ export default Util

// better import { Util } from ‘./util’ export default Util

同个文件每个模块只允许 import 一次,有多个 import 请书写在一起 原因:这样可以让代码更易于维护 // bad import foo from ‘foo’ // … some other imports … // import { named1, named2 } from ‘foo’

// good import foo, { named1, named2 } from ‘foo’

// good import foo, { named1, named2 } from ‘foo’

将所有 import 语句放在文件最前方 // bad import foo from ‘foo’ foo.init()

import bar from ‘bar’

// good import foo from ‘foo’ import bar from ‘bar’

foo.init()

多行导入应该像多行数组和对象文字一样缩进 // bad import { longNameA, longNameB, longNameC, longNameD, longNameE } from ‘path’

// good import { longNameA, longNameB, longNameC, longNameD, longNameE } from ‘path’

对象属性 使用 . 来访问对象属性const joke = { name: ‘haha’, age: 28}// badconst name = joke[‘name’]// goodconst name = joke.name 当访问的属性是变量时使用 [] const luke = { jedi: true, age: 28, }

function getProp (prop) { return luke[prop] }

const isJedi = getProp(‘jedi’)

变量声明 声明变量时,请使用 const、let 关键字,如果没有写关键字,变量就会暴露在全局上下文中,这样很可能会和现有变量冲突,另外,也很难明确该变量的作用域是什么。这里推荐使用 const 来声明变量,我们需要避免全局命名空间的污染。eslint: no-undef prefer-const// baddemo = new Demo()// goodconst demo = new Demo() 将所有的 const 和 let 分组// badlet aconst blet cconst dlet e// goodconst bconst dlet alet clet e 变量不要进行链式赋值 原因:变量链式赋值会创建隐藏的全局变量 // bad (function example() { // JavaScript interprets this as // let a = ( b = ( c = 1 ) ); // The let keyword only applies to variable a; variables b and c become // global variables. let a = b = c = 1 }())

console.log(a) // throws ReferenceError console.log(b) // 1 console.log(c) // 1

// good (function example() { let a = 1 let b = a let c = a }())

console.log(a) // throws ReferenceError console.log(b) // throws ReferenceError console.log(c) // throws ReferenceError

// the same applies for const

不允许出现未被使用的变量,eslint: no-unused-vars 原因:声明但未被使用的变量通常是不完全重构犯下的错误.这种变量在代码里浪费空间并会给读者造成困扰 // bad

var some_unused_var = 42

// Write-only variables are not considered as used. var y = 10 y = 5

// A read for a modification of itself is not considered as used. var z = 0 z = z + 1

// Unused function arguments. function getX (x, y) { return x }

// good

function getXPlusY (x, y) { return x + y }

const x = 1 const y = a + 2

alert(getXPlusY(x, y))

// ‘type’ is ignored even if unused because it has a rest property sibling. // This is a form of extracting an object that omits the specified keys. const { type, …coords } = data // ‘coords’ is now the ‘data’ object without its ‘type’ property.

比较运算符&相等 使用 === 和 !== 而非 == 和 !=,eslint: eqeqeq 条件声明例如 if 会用 ToBoolean 这个抽象方法将表达式转成布尔值并遵循如下规则 Objects 等于 true Undefined 等于 false Null 等于 false Booleans 等于 布尔值 Numbers 在 +0, -0, 或者 NaN 的情况下等于 false, 其他情况是 true Strings 为 ‘’ 时等于 false, 否则是 trueif ([0] && []) { // true // 数组(即使是空数组)也是对象,对象等于true}