前端开发规范

一、命名规范

1、文件命名

文件夹/文件的命名统一用小写,使用短横线命名 (kebab-case),包括jscsshtml文件。

案例

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 块的中的元素。


search 块

元素(Element)

一个元素是块的一部分,具有某种功能。元素是依赖上下文的:它们只有处于他们应该属于的块的上下文中时才是有意义的。

例如一个输入域和一个按钮是 Search 块的中的元素。

search 块中的元素

用块与元素来描述页面

页面的内容由块和元素构成,每个块都可以看成一个组件,部分公用性比较强的元素,也可以看成一个组件。

一个复杂块里有可能再嵌套多个单一功能块。

例如,一个 Head 块会包含其他块:

header块

每一个块和元素,都应该有对应的关键字。

用来标识一个具体块的关键字其实就是这个块的名字(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 来规范 jscss 代码格式

  • 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/changedom 事件或者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
}
控制函数的副作用

setupdate等动词前缀的方法,一般用来修改某个全局变量,有一定副作用,除了修改 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')

遵循一个函数只做一件事的原则,我们可以将上述功能拆成两个函数fetchMultipleUserfetchSingleUser 来实现。在需要获取用户数据时,只需要选择调用其中的一个函数。

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>

基础组件名用特定前缀开头

应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 BaseAppV

// 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

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343