这里主要分为Vue风格指南和customer自定义风格,后续应该会继续更新。
vue风格指南
优先级A(必要的)
组件名为多个单词
export default {
name: 'vue-calendar',
components: {
'calendar-body': CalendarBody,
'calendar-header': CalendarHeader
}
}
组件data必须是一个函数
export default {
data () {
return {
foo: 'bar'
}
}
}
Prop定义应该详细
设置required的情况下,需要设置validator。
未设置required的数据,基本数据类型需要加上default,对于对象类型,建议添加validator进行数据验证。
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}
为v-for设置键值
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
避免v-if和v-for用在一起
- 为了过滤一个列表中的项目 (比如 v-for="user in users" v-if="user.isActive")。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。
- 为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将 v-if 移动至容器元素上 (比如 ul, ol)。
为组件样式设置作用域
除却顶级App组件,所有组件遵循下面规则
- 添加scoped特性
- 组件内只存在一个一级子元素,用于进行class设置作用域
<template>
<div class="vue-calendar">
<calendar-header
:observer="vueCalendarObserver"/>
<calendar-body
:observer="vueCalendarObserver"
:weekLabelIndex="weekLabelIndex"
@dayClick="dayClick" />
</div>
</template>
<script>
import { initObserver } from './lib/Util.js'
import CalendarBody from './Component/CalendarBody'
import CalendarHeader from './Component/CalendarHeader'
export default {
name: 'vue-calendar',
components: {
'calendar-body': CalendarBody,
'calendar-header': CalendarHeader
},
props: {
weekLabelIndex: {
type: Number,
default: 1
}
},
data () {
return {
// 观察者对象
vueCalendarObserver: {}
}
},
created () {
this.vueCalendarObserver = initObserver()
},
methods: {
dayClick (dayItem) {
this.$emit('dayClick', dayItem)
}
}
}
</script>
<style lang="stylus" scoped>
.vue-calendar
background #7BDCFE
font-size 14px
font-family sans-serif
</style>
私有属性名
在插件、混入等扩展中始终为自定义的私有属性使用 $_ 前缀。并附带一个命名空间以回避和其它作者的冲突 (比如 $_yourPluginName_)。
优先级B(强烈推荐的)
组建文件
只要有能够拼接文件的构建系统,就把每个组件单独分成文件。就是将组件内部的可进行提取组件的代码片段提取成组件并放置在当前文件夹/components/下。
components/
|- TodoList.js
|- TodoItem.js
单文件组件的大小写
单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)。个人建议保持单词大写开头。
components/
|- MyComponent.vue
单例组件名
只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。不接受prop的组件,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文。
components/
|- TheHeading.vue
|- TheSidebar.vue
紧密耦合的组件名
和父组件紧密耦合的子组件应该以父组件名作为前缀命名。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。例如下面navigation组件应用与searchSidebar组件中,所以命名沿袭父组件的名称风格。
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
组件名中的单词顺序
组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。这里我的理解是高级别是用来修饰组件的,描述性的修饰词结尾,则是修饰具体功能的。例如下面SearchButtonClear组件,Search指的是当前组件为查询按钮组件,Clear指的是当前组件的具体功能,查询组件清空查询条件的。
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
自闭合组件
在单文件组件、字符串模板和 JSX 中没有内容的组件应该是自闭合的——但在 DOM 模板里永远不要这样做。自闭合组件表示它们不仅没有内容,而且刻意没有内容。如果组件不使用插槽的情况下,则永远使用自闭合。如果存在插槽的情况,则不使用自闭合。
<my-component/>
<my-component>
<div></div>
</my-component>
模板中的组件名大小写
在所有的地方都使用 kebab-case 。
<!-- 在所有地方 -->
<my-component />
完整单词的组件名
组件名应该倾向于完整单词而不是缩写
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
Prop 名大小写
在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case。
props: {
greetingText: String
}
<WelcomeMessage greeting-text="hi"/>
多个特性的元素
多个特性的元素应该分多行撰写,每个特性一行。
<img
src="https://vuejs.org/images/logo.png"
alt="Vue Logo"
>
模板中简单的表达式
应该把复杂计算属性分割为尽可能多的更简单的属性。
computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}
带引号的特性值
非空 HTML 特性值应该始终带引号
<input type="text">
<AppSidebar :style="{ width: sidebarWidth + 'px' }">
指令缩写
指令缩写 (用 : 表示 v-bind: 和用 @ 表示 v-on:) 应该要么都用要么都不用。个人习惯都不用。
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
<input
@input="onInput"
@focus="onFocus"
>
customer自定义
代码层级
命名规范
文件夹命名
- 全局通用的组件放在 /src/components下,通用组件components下组件内部最多允许存在一层文件夹,包含index.vue。例如下面日历组件,因为日历组件相对来说复杂一些,所以内部存在components,lib文件夹。这一层是基本上已经是最后一层文件夹。如果不存在较为复杂的逻辑,不建议components下继续新建文件夹。
- 业务页面中的组件,放在各自页面下的 ./components文件夹下,如果不存在较为复杂的逻辑,不建议components下继续新建文件夹。并且内部组件名称尽量沿袭父组件的命名,根据“紧密耦合的组建名”。
- 属于组件或类的,统一使用大写字母开头的(PascalCase)命名规范
- 其他非组件或类的,统一使用小写字母开头的(kebab-case)命名规范
- views下的模块文件夹小写除了components下的子文件夹
文件命名
- *.js 文件,统一使用PascalBase风格,除了index.js
- *.vue文件,统一使用PascalBase风格,除了index.vue
- *.css文件,css文件统一使用kebab-case风格
变量命名
特别说明:
- 类名必须是有意义的名词
- boolean返回值类型的变量应该存在is/has标识
- 方法为动宾短语(页面跳转:navigateHomePage,表单操作:handleSave/handleUpdate/handleDelete,获取数据api封装:getListData,组件内部util方法视情况而定,以动词开头)
- 事件方法以 on 开头(onTypeChange、onUsernameInput)
组件内部书写规范
html
- 标签名必须使用小写字母
- 属性名必须使用小写字母
- 属性值必须用双引号包围
- html class命名划分页面,并且加上注释表明当前组件的页面组成部分
- 组件内容利用组件嵌套,提高代码阅读性。例如table,search, 或者将页面内容化块,每一块提出子组件。不希望一个组件内有大量的代码,阅读性不高,并且不易维护。
- element 标签内绑定多个属性/方法,需要以多行的方式书写,顺序为: class, 属性绑定,方法绑定,v-if/v-for
- 组件书写不存在插槽的情况下,使用自闭合方式
- html所有的样式需要包含在同一个class里
- html组件书写kebab-case方式
- class书写为kebab-case方式
<template>
<div class="calendar-body">
<!-- 日历周label标识 -->
<div class="calendar-body-week-label">
<div
class="calendar-body-week-label-day"
:class="{'red-font': isShowRedColorForWeekLable()}"
v-for="(weekLabelItem, index, key) in weekLabelArray"
:key=key
>
<span>{{weekLabelItem}}</span>
</div>
</div>
<!-- 日历数据,遍历日历二位数组,得到每一周数据 -->
<div
class="calendar-body-week"
v-for="(weekItem, key) in weekList"
:key=key
>
<!-- 遍历每一周数据 -->
<div
class="calendar-body-week-day"
:class="{'calendar-body-current-month': dayItem.isCurrentMonth, 'calendar-body-current-day': dayItem.isCurrentDay, 'red-font': isShowRedColorForWeekLable()}"
@click="onClickDay(dayItem)"
v-for="(dayItem, index, key) in weekItem"
:key=key
>
<span>{{dayItem.monthDay}}</span>
</div>
</div>
</div>
</template>
script
常用成员放置顺序
之所以将data/computed/watch放置created之前,因为create的时候数据data已经初始成功
- name
- components
- props
- data
- computed
- watch
- created
- mounted
- update
- beforeRouteUpdate
- metods
components在html里使用按照kabeb-case风格
components: {
'calendar-body': CalendarBody,
'calendar-header': CalendarHeader
},
props定义应该详细,具体信息参考:props定义应该详细和prop大小写
<calendar-body
:observer="vueCalendarObserver"
:week-label-index="weekLabelIndex"
@dayClick="handleDayClick" />
props: {
weekLabelIndex: {
type: Number,
default: 1
}
},
props数据处理
对于父子组件嵌套传值,子组件通过Props接受,如果子组件只是显示信息,不存在修改信息。可以直接使用props传递的值,如果需要修改,则需要通过深复制创建新对象,因为props的值不允许改变。具体信息参考文章prop初始化数据处理
深复制方式:
- (1) 如果props值只有一级属性例如: var x = {'name': 'rodchen'},则使用 Object.assign({}, x)
- (2) 如果props值存在大于一级属性例如: var x = {'name': 'rodchen', 'information': {'age', 26}},则使用 JSON.parse(JSON.stringify(x))
组件引入过多项目时,需要每一个项目占有一行
import {
getHeaderContent,
getFirstDayOfMonth,
getFirstDayOfNextMonth,
getFirstDayOfPrevMonth
} from '../lib/Util.js'
组件引入其他组件的,需要将公共组件和内部组件空格分开,加以说明
components: {
// 公共组件 Todo
// 内部组件
'calendar-body': CalendarBody,
'calendar-header': CalendarHeader
},
data数据过多的情况下,需要添加注释进行区分
- 页面绑定数据,
- 页面判断标志数据,
- 表格数据(form, 分页设置,查询字符)
watch慎用,不是watch情况下不建议使用watch,例如同一组件在多个路由使用,需要使用watch进行同一组件在不同路由下的区分。
data最早可以在create生命周期进行操作
元素操作最早可以在monted生命周期中进行操作
不要使用this.$parent,尽量避免使用this.$refs,通过组件通信进行数据交互
methods中的方法添加注释进行分类说明
一般分为:
- 数据处理,
- 页面操作,
- 工具函数 (标明工具函数的步骤注释)
methods: {
/**
* 日历方法
*/
// 点击日历
onClickDay (dayItem) {
this.$emit('dayClick', dayItem)
},
// 设置weekList值
setWeekListValue (firstDayOfmonth) {
this.weekList = []
let dayOfCalendar = getFirstDayOfCalendar(firstDayOfmonth, this.weekLabelIndex)
// 遍历层数为6,因为日历显示当前月数据为6行
for (let weekIndex = 0; weekIndex < 6; weekIndex++) {
let weekItem = []
// 每一周为7天
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
let dayItem = {
date: dayOfCalendar,
monthDay: formatDayWithTwoWords(dayOfCalendar.getDate()),
isCurrentMonth: isCurrentMonth(this.firstDayOfMonth, dayOfCalendar),
isCurrentDay: isCurrentDay(dayOfCalendar)
}
weekItem.push(dayItem)
// 当前日期加1,以此类推得到42条记录
dayOfCalendar.setDate(dayOfCalendar.getDate() + 1)
}
this.weekList.push(weekItem)
}
},
/**
* 观察者模式相关方法
*/
// 切换月份更新body数据
update (content) {
this.firstDayOfMonth = content
this.setWeekListValue(content)
},
/**
* 工具方法
*/
// 周六/周日标识红色字体
isShowRedColorForWeekLable (index) {
return index + this.weekLabelIndex === 6 ||
index + this.weekLabelIndex === 7 ||
(index === 0 && this.weekLabelIndex === 0)
}
}
css
style上需要添加scoped
css只有一个最外层class页面布局,页面的主框架是div + css, 在进行div布局之后,涉及到多个div同行,建议使用flex布局
css属性的书写顺序(也是之前没注意的,正好收集学习一下)
- 定位属性:position display float left top right bottom overflow clear z-index
- 自身属性:width height margin padding border background
- 文字样式:font-family font-size font-style font-weight font-varient color
- 文本属性:text-align vertical-align text-wrap text-transform text-indent text-decoration letter-spacing word-spacing white-space text-overflow
- css3中新增属性:content box-shadow border-radius transform
系统层架
页面提示语的确定
- 表单页面提交不需要confirm提示语
- 数据删除/列表页更新状态需要confirm提示语
- 新建页面路由跳转离开是否需要提示语
公共样式的提出以及公共变量的使用
- 保持整个页面风格,页面引入公共样式里的基本样式变量值
- 公共样式存在src/assets下
- 如果只是模块内存在公共样式,那么可以在模块内创建assets提取模块公共样式
页面重用的设定
这里的页面指的是具有完整功能的组件,例如创建人员组件。一帮情况下可能会将两个页面公用,然后页面进行判断。这里建议将页面form表单提出公共组件,然后页面还是创建多个文件,因为完整功能文件多重用会造成页面逻辑相对来说混乱,不易维护。
不会存在多重用页面组件,只会存在多重用功能组件。
系统中的数字量处理
- 页面中不允许出现数字量,需要创建枚举类,并且按照上面设定的枚举类命名风格
- 出现的枚举类如果是多个页面公用,则需要提取到公共变量文件中去
form表单的处理
- form表单必填项验证
- form表单必填项/非必填项的长度验证(依赖于数据库设定或者也存在统一长度限制)
- form表单数字验证/电话验证/邮件验证
- form表单日期范围验证的设定,startDate的日期范围验证是否是只可以点击当天之前/当天之后,endDate的选择开始日期一般为startDate的日期之后
- form表单的特殊字符验证
页面查询条件是否保存设定
列表页面查询条件设置之后,点击新建页面,然后返回列表页面,此时页面上的查询条件需不需要保存,规则建议如下:
- 新建页面点击取消,在不造成数据库数据更新的情况下保存查询条件
- 如果进行保存,数据库数据更新,则所有查询条件清空,数据列表显示最新一条数据
其他模块集成问题
开发前期可能存在依赖其他模块内容,假设业务开发依赖于基本人员信息(角色/员工工号),则应前期在添加公共mock代码。用于所有依赖的地方使用,而且尽可能的调用方式和集成其他模块之后相同,这样相当与将与其他模块的集成使用mock代码形成一个黑盒。
公共组件的书写
公共组件具有的特性
- 黑盒性:调用者不用关心组件代码的实现,通过组件描述可以直接了解其使用
- 独立性:不针对单个或者部分调用者进行特殊处理,容易造成代码混乱,特殊处理需要放大炮调用者自身处理
- 自定义性:提供调用者自定义的数据接口,以及插槽的使用,调用者是否需要自定义元素内容
公共组件的代码规范
公共组件需要存在一个文件对组件进行说明文档,可以在公共组件添加readme.md文件,内部格式如下:
/**
* 组件名称
* @module 组件存放位置
* @desc 组件描述
* @author 组件作者
* @date 2017年12月05日17:22:43
* @param {Object} [title] - 参数说明
* @param {String} [columns] - 参数说明
* @example 调用示例
*/
<hbTable :title="title" :columns="columns" :tableData="tableData"></hbTable>
前后端交互规范
- 后台传回的model在满足前台需求的情况下,尽可能的简单,不建议存在返回的对象有许多用不到的字段信息
- 后台返回的数字量(比如数据的状态),一并返回数字量的文字描述
- 前端在得到后台返回数据之后可以通过model进行转换,一方面可以进行数据检测,第二也是可以规避一些前台不需要的字段
- 前台浏览器地址栏不允许出现数据表中具有实际意义的主键,而是无实际意义的uuid的值
- 后台数据库的日期类型一律建议存储为DateTime类型,不要因为前台关注的日期,就存储为date类型。因为多时区问题会造成date类型存储的日期不正确,而DateTime类型可以通过后台自动处理掉