一、命名规范
1、文件命名
文件夹/文件的命名统一用小写,使用短横线命名 (kebab-case),包括js
、css
、html
文件。
案例
assets/
|-- css/
|--- reset.css
|-- image/
|--- icon-logo.png
api/
|-- ajax.js
components/
|- home-header/
|- home-main/
|- home-main/
pages/
|-- PageHome.vue
helpers/
|-- util.js
...
index.html
admin.html
2、组件命名
vue
组件命名统一大写单词开头,使用驼峰式命名(PascalCase),组件名应该始终是多个单词。
案例
components/
|- home-header/
|-- HeaderLogo.vue
|-- HeaderSearch.vue
|- home-main/
|- home-footer/
pages/
|-- PageHome.vue
二、html 规范
html 标签语义化
-
<header>
:定义文档或者文档的部分区域的页眉,应作为介绍内容或者导航链接栏的容器。 -
<nav>
:描述一个含有多个超链接的区域,该区域包含跳转到其他页面或页面内部其他部分的链接列表。 -
<main>
:定义文档的主要内容,该内容在文档中应当是独一无二的,不包含任何在文档中重复的内容,比如侧边栏,导航栏链接,版权信息,网站 logo,搜索框(除非搜索框作为文档的主要功能)。 -
<article>
:表示文档、页面、应用或网站中的独立结构,是可独立分配的、可复用的结构,如在发布中,它可能是论坛帖子、杂志或新闻文章、博客、用户提交的评论、交互式组件,或者其他独立的内容项目。 -
<aside>
:表示一个和其余页面内容几乎无关的部分,被认为是独立于该内容的一部分且可以被单独的拆分出来而不会影响整体。通常表现为侧边栏或嵌入内容。 -
<footer>
:定义最近一个章节内容或者根节点元素的页脚。一个页脚通常包含该章节作者、版权数据或者与文档相关的链接等信息,使用 footer 插入联系信息时,应在footer
元素内使用<address>
元素。 -
<section>
:表示文档中的一个区域(或节),比如,内容中的一个专题组。
如果元素内容可以分为几个部分的话,应该使用 <article>
而不是 <section>
。
不要把 <section>
元素作为一个普通的容器来使用,特别是当<section>
仅仅是为了美化样式或方便脚本使用的时候,应使用<div>
。
通俗来说就是<article>
比<section>
更具有独立性、完整性。可通过该段内容脱离了所在的语境,是否完整、独立来判断。
页面基本结构:
三、css 规范
使用
BEM
规范进行css
命名
BEM 规范
BEM 代表块(Block),元素(Element),修饰符(Modifier)
编程方法论中一个最常见的例子就是面向对象编程(OOP)。这一编程范例出现在许多语言中。在某种程度上,BEM 和 OOP 是相似的。
块(Block)
使用 vue 进行开发,一个组件就是一个
Block
一个块是一个独立的实体,就像应用的一块“积木”。一个块既可以是简单的也可以是复合的(包含其他块)。
例如一个输入域和一个按钮是 Search 块的中的元素。
元素(Element)
一个元素是块的一部分,具有某种功能。元素是依赖上下文的:它们只有处于他们应该属于的块的上下文中时才是有意义的。
例如一个输入域和一个按钮是 Search 块的中的元素。
用块与元素来描述页面
页面的内容由块和元素构成,每个块都可以看成一个组件,部分公用性比较强的元素,也可以看成一个组件。
一个复杂块里有可能再嵌套多个单一功能块。
例如,一个 Head 块会包含其他块:
每一个块和元素,都应该有对应的关键字。
用来标识一个具体块的关键字其实就是这个块的名字(block name
)。
例如,menu
可以作为Menu
块的关键字,head
可以作为Head
块的关键字。
例如,菜单中的每个菜单项就是menu
块的item
元素。
一个块范围内的一种元素的名字也必须是唯一的。一种元素可以重复出现多次。
例如上面的 head 块,可以这样分解
<block:xxx>
表示一个块,<element:column>
表示一个元素
<block:page>
<block:head>
<block:menu> … </block:menu>
<element:column>
<block:logo/>
</element:column>
<element:column>
<block:search>
<element:input/>
<element:button>Search</element:button>
</block:search>
</element:column>
<element:column>
<block:auth> … </block:auth>
<element:column>
</block:head>
</block:page>
这种结构可以叫做 BEM 树(和 DOM 树类似)。
块的独立性
一个独立的块等于一个独立的组件,可以放置在页面的任意位置 ,包括嵌套在其他块里。
使用 BEM 规范来命名 CSS
独立的 css
从 CSS 的角度来看:
- 一个块(或者一个元素)必须有一个唯一的“名字”(一个 CSS 类)这样才能被 CSS 规则所作用。
- HTML 元素不能用作 CSS 选择器(如.menu td)因为这样的选择器并非是完全上下文无关的。
- 避免使用级联(cascading)选择器(注:如.menu .item)。
下面是一种可能的 CSS 类命名方案:
一个元素的 CSS 类名是一个块名和一个元素名的组合,它们中间用一些符号隔开。
- block: menu
- element: item
- modifier: active
<!-- block menu -->
<ul class="menu">
<li class="menu-item">…</li>
<li class="menu-item">…</li>
<li class="menu-item menu-item-active">…</li>
</ul>
<!-- block search-form -->
<form class="search-form search-form-theme-gray">
<div class="search-form-content">
<input class="search-form-input"/>
<button class="search-form-button search-form-button-disable"></button>
</div>
</form>
一个相对复杂的例子
下面是根据之前的 header 例子创建的一个对应的 html 结构
1、根据页面结构生成 bem 树结构
<block:page>
<block:head>
<block:menu> … </block:menu>
<element:column>
<block:logo/>
</element:column>
<element:column>
<block:search>
<element:input/>
<element:button>Search</element:button>
</block:search>
</element:column>
<element:column>
<block:auth> … </block:auth>
<element:column>
</block:head>
</block:page>
2、转换成对应的 dom
树以及对应的 class
命名
<article class="home">
<!-- block header -->
<header class="header">
<!-- element header-menu -->
<!-- block menu -->
<ul class="menu header-menu">
<!-- element menu-item -->
<li class="menu-item"></li>
<li class="menu-item"></li>
<li class="menu-item menu-item-active"></li>
<li class="menu-item"></li>
</ul>
<!-- block logo -->
<section class="logo">
<!-- element logo-txt -->
<span class="logo-txt">logo</span>
<i class="logo-icon"></i>
</section>
<!-- block search -->
<section class="search">
<input class="search-ipt" type="text">
<button class="search-btn search-btn-disable"></button>
</section>
<!-- block auth -->
<section class="auth">
<input class="auth-username" type="text">
<input class="auth-password" type="password">
</section>
</header>
<!-- block main -->
<main class="main"></main>
<!-- block footer -->
<footer class="footer"></footer>
</article>
3、使用 vue 组件进行分解
<!-- HeaderMenu.vue -->
<template>
<ul class="menu">
<li class="menu-item"></li>
<li class="menu-item"></li>
<li class="menu-item menu-item-active"></li>
<li class="menu-item"></li>
</ul>
</template>
<!-- HeaderLogo.vue -->
<template>
<section class="logo">
<span class="logo-txt">logo</span>
<i class="logo-icon"></i>
</section>
</template>
<!-- HeaderSearch.vue -->
<template>
<section class="search">
<input class="search-ipt" type="text">
<button class="search-btn search-btn-disable"></button>
</section>
</template>
<!-- HeaderAuth.vue -->
<template>
<section class="auth">
<input class="auth-username" type="text">
<input class="auth-password" type="password">
</section>
</template>
<!-- HomeHeader.vue -->
<template>
<header class="header">
<header-menu/>
<header-logo/>
<header-search/>
<header-auth/>
</header>
</template>
<!-- HomeMain.vue -->
<template>
<main class="main">...</main>
</template>
<!-- HomeFooter.vue -->
<template>
<footer class="footer">...</footer>
</template>
<!-- PageHome.vue -->
<template>
<home-header/>
<home-main/>
<home-footer/>
</template>
4、文件结构
components/
|- home-header/
|-- HomeHeader.vue
|-- HeaderMenu.vue
|-- HeaderLogo.vue
|-- HeaderSearch.vue
|-- HeaderAuth.vue
|- HomeMain.vue
|- HomeFooter.vue
pages/
|- PageHome.vue
四、js 规范
1、使用 prettier
来规范 js
与 css
代码格式
- vscode 的插件中搜索
prettier
,进行安装 - 在文件-首选项-设置中,将以下配置加到
User Settings
配置文件
"editor.formatOnSave": true
"prettier.singleQuote": true,
"prettier.semi": false
2、js 代码规范
2.1 变量
- 命名方式:小驼峰
- 命名规范:前缀名词
- 命名建议:语义化
变量声明要根据上下文环境语义化声明,不能使用无任何语义的关键词或者数字来表示变量
除了一些约定俗成的简写,正常情况下尽量不使用简写声明变量
需要做到看到变量名称,就知道这个变量是用来做什么的
// bad
let setCount = 10
let input1 = document.querySelector('#username')
let isUserActive = true
// good
let maxCount = 10
let inputUser = document.querySelector('#username')
let userActive = true
2.2 常量
- 命名方式:全部大写
- 命名规范:使用大写字母和下划线来组合命名,下划线用以分割单词
- 命名建议:语义化
案例
// bad
async function fetchSomething(data) {
let result = await api.post('http://www.baidu.com', data)
return result
}
function isCurrentCount(count) {
return count < 10
}
// good
// config.js
const MAX_COUNT = 10
const API_ROOT = 'http://www.baidu.com'
// xxx.js
import { MAX_COUNT, API_ROOT } from './config.js'
async function fetchSomething(data) {
let result = await api.post(API_ROOT, data)
return result
}
function isCurrentCount(count) {
return count < MAX_COUNT
}
2.3 函数
命名规范
- 命名方式:小驼峰式命名法。
- 命名规范:前缀应当为动词。
- 命名建议:语义化。
可以参考如下的动作:
- has: 判断是否含有某个值
- is: 判断是否为某个值
- get: 获取某个值
- set: 设置某个值
- update: 更新某个值
- fetch: ajax 请求(一般用在
vuex
里的actions
) - on: 触发事件(
click/change
等dom
事件或者emit
派发事件) - render: 渲染页面
- handle: 执行某一个事件(如果不清楚用什么动词前缀,可以使用
handle
)
还有很多类似的动作,例如:add/delete/put/select/change/move/remove/to
等
案例:
// 接口请求
async function fetchUserInfo(id) {
const result = await request.post('/api/userInfo', { id })
return result
}
// 判断是否含有username
function hasUserName(user) {
// doSomething
if (user.name === xxx) {
return true
} else {
return false
}
}
// 获取用户信息
function getUserInfo(id) {
let userInfo = this.fetchUserInfo(id)
return {
name: userInfo.name,
role: userInfo.role
}
}
// 触发某个dom事件
function onUserIptBlur(e) {
let username = e.target.value
this.changeUserName(username)
}
function changeUserName(username) {
this.username = username
}
// 渲染登录浮层
function renderLoginModel() {
this.loginModelVisible = true
}
控制函数的副作用
set
和update
等动词前缀的方法,一般用来修改某个全局变量,有一定副作用,除了修改 vuex
修改状态使用,其它情况建议使用get
返回一个新的修改后的对象,然后在handle
方法中修改该全局变量
不要修改函数的入参
案例:
// bad
function addRoleToUser(user) {
let role = await fetchUserRole()
user.role = role
}
// good
function getRoleUser(user) {
let role = await fetchUserRole()
return {role, ...user}
}
// vuex中,actions和mutaions命名可以使用set/add/update等动词前缀
mutations = {
updateUserInfo(state, userInfo) {
state.userInfo = userInfo
}
}
actions = {
setUserInfo({ commit }, data) {
apis.fetchUserInfo(data).then(res => {
let userInfo = res.result
commit('updateUserInfo', userInfo)
})
}
}
// 如果确实需要修改数据,可以走vuex数据流
...mapMutations(['updateUserInfo'])
function changeUserRole(user) {
let roleUser = this.getRoleUser(user)
this.updateUserInfo(roleUser)
}
// 或者
function changeUserRole(user) {
this.roleUser = this.getRoleUser(user)
}
无副作用的函数,是不依赖上下文,也不改变上下文的函数
案例:
// bad
async function addFavoritesToUser(user) {
const result = await fetchUserFavorits(user.id)
user.favoriteBooks = result.books
user.favoriteSongs = result.songs
user.isMusicFan = result.songs.length > 100
}
// good
async function getUserDetail(user) {
const { books, songs, isMusicFan } = await getUserFavorites(id)
return Object.assign(user, { books, songs, isMusicFan })
}
async function getUserFavorites(id) {
const { books, songs } = await fetchUserFavorits(user.id)
return {
books,
songs,
isMusicFan: result.songs.length > 100
}
}
最小函数准则
一个函数只做一件事情,提高代码可维护性和模块化
// bad
async function fetchUserInfo(id) {
const isSingle = typeof idList === 'string'
const idList = isSingle ? [id] : id
const result = await request.post('/api/userInfo', { idList })
return isSingle ? result[0] : result
}
const userList = await fetchUserInfo(['1011', '1013'])
const user = await fetchUserInfo('1017')
遵循一个函数只做一件事的原则,我们可以将上述功能拆成两个函数fetchMultipleUser
和 fetchSingleUser
来实现。在需要获取用户数据时,只需要选择调用其中的一个函数。
async function fetchMultipleUser(idList) {
return await request.post('/api/users/', { idList })
}
async function fetchSingleUser(id) {
return await fetchMultipleUser([id])[0]
}
上述改良不仅改善了代码的可读性,也改善了可维护性。举个例子,如果后期需要去除单一查询功能,按照未改良前的逻辑,需要在函数内部进行变动,而且很担心会有其它问题。按照改良后的版本,直接去掉fetchSingleUser
方法就行
五、组件规范
每个 Vue 组件的代码建议不要超出 200 行,如果超出建议拆分组件。
组件一般情况下是可以拆成基础/ui 部分和业务部分,基础组件一般是承载呈现,基础功能,不和业务耦合部分。
业务组件一般包含业务功能业务特殊数据等等。
组件规范
1、UI 组件/基础组件
放在src/components
里
UI 组件可以是某个页面的一块block
,和业务关联性较强,数据由容器组件通过 props
传给 ui 组件,容器组件由 ui 组件组成
基础组件可以是公共组件,业务性较弱,通用性强,可以包含一些公共 mixin
参考目录结构:
components/
|- base/
|-- BaseDialog.vue
|-- BaseToast.vue
|- home-header/
|-- HomeHeader.vue
|-- HeaderMenu.vue
|- HomeMain.vue
|- HomeFooter.vue
2、容器组件
放在src/pages
里
和当前业务耦合性比较高,由多个基础组件组成,可承载当前页的业务接口请求和数据(vuex
)。
容器组件获取vuex
相关状态,通过props
传递数据给 ui 组件或者基础组件,通过$emit
来获取子组件分发的数据
参考目录结构:
pages/
|- PageHome.vue
组件开发风格
组件名为多个单词
组件名应该始终是多个单词的,根组件 App
除外。
这样做可以避免跟现有的以及未来的 HTML
元素相冲突,因为所有的 HTML
元素名称都是单个单词的。
// bad
Vue.component('todo', {
// ...
})
export default {
name: 'Todo',
// ...
}
// good
Vue.component('todo-item', {
// ...
})
export default {
name: 'TodoItem',
// ...
}
组件命名的大小写
单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)。
组件文件命名建议统一
PascalCase
// bad
components/
|- mycomponent.vue
components/
|- myComponent.vue
// good
components/
|- MyComponent.vue
components/
|- my-component.vue
组件引用的大小写
在 js/vue
文件内部的组件名可以是PascalCase
或者kebab-cas
,在 dom
模板中始终是 kebab-case
的
建议在所有地方使用
kebab-case
引用
<!-- bad -->
<!-- 在单文件组件和字符串模板中 -->
<mycomponent/>
<!-- 在单文件组件和字符串模板中 -->
<myComponent/>
<!-- 在 DOM 模板中 -->
<MyComponent></MyComponent>
<!-- good -->
<!-- 在单文件组件和字符串模板中 -->
<MyComponent/>
<!-- 在 DOM 模板中 -->
<my-component></my-component>
<!-- 或者在所有地方 -->
<my-component></my-component>
基础组件名用特定前缀开头
应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base
、App
或 V
。
// bad
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
// good
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
单例组件名用 The 前缀
只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。
这不意味着组件只可用于一个单页面,而是每个页面只使用一次。这些组件永远不接受任何 prop,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文。如果你发现有必要添加 prop,那就表明这实际上是一个可复用的组件,只是目前在每个页面里只使用一次。
// bad
components/
|- Heading.vue
|- MySidebar.vue
// good
components/
|- TheHeading.vue
|- TheSidebar.vue
紧密耦合的组件名
和父组件紧密耦合的子组件应该以父组件名作为前缀命名。
如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。
// bad
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
// good
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue