前端開(kāi)發(fā)規(guī)范

作者:taomas
鏈接:http://www.lxweimin.com/p/58417e0ae693

一、命名規(guī)范

1、文件命名

文件夾/文件的命名統(tǒng)一用小寫,使用短橫線命名 (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組件命名統(tǒng)一大寫單詞開(kāi)頭,使用駝峰式命名(PascalCase),組件名應(yīng)該始終是多個(gè)單詞。

案例

components/
|- home-header/
|-- HeaderLogo.vue
|-- HeaderSearch.vue
|- home-main/
|- home-footer/
pages/
|-- PageHome.vue

二、html 規(guī)范

html 標(biāo)簽語(yǔ)義化

  • <header>:定義文檔或者文檔的部分區(qū)域的頁(yè)眉,應(yīng)作為介紹內(nèi)容或者導(dǎo)航鏈接欄的容器。
  • <nav>:描述一個(gè)含有多個(gè)超鏈接的區(qū)域,該區(qū)域包含跳轉(zhuǎn)到其他頁(yè)面或頁(yè)面內(nèi)部其他部分的鏈接列表。
  • <main>:定義文檔的主要內(nèi)容,該內(nèi)容在文檔中應(yīng)當(dāng)是獨(dú)一無(wú)二的,不包含任何在文檔中重復(fù)的內(nèi)容,比如側(cè)邊欄,導(dǎo)航欄鏈接,版權(quán)信息,網(wǎng)站 logo,搜索框(除非搜索框作為文檔的主要功能)。
  • <article>:表示文檔、頁(yè)面、應(yīng)用或網(wǎng)站中的獨(dú)立結(jié)構(gòu),是可獨(dú)立分配的、可復(fù)用的結(jié)構(gòu),如在發(fā)布中,它可能是論壇帖子、雜志或新聞文章、博客、用戶提交的評(píng)論、交互式組件,或者其他獨(dú)立的內(nèi)容項(xiàng)目。
  • <aside>:表示一個(gè)和其余頁(yè)面內(nèi)容幾乎無(wú)關(guān)的部分,被認(rèn)為是獨(dú)立于該內(nèi)容的一部分且可以被單獨(dú)的拆分出來(lái)而不會(huì)影響整體。通常表現(xiàn)為側(cè)邊欄或嵌入內(nèi)容。
  • <footer>:定義最近一個(gè)章節(jié)內(nèi)容或者根節(jié)點(diǎn)元素的頁(yè)腳。一個(gè)頁(yè)腳通常包含該章節(jié)作者、版權(quán)數(shù)據(jù)或者與文檔相關(guān)的鏈接等信息,使用 footer 插入聯(lián)系信息時(shí),應(yīng)在 footer 元素內(nèi)使用 <address> 元素。
  • <section>:表示文檔中的一個(gè)區(qū)域(或節(jié)),比如,內(nèi)容中的一個(gè)專題組。

如果元素內(nèi)容可以分為幾個(gè)部分的話,應(yīng)該使用 <article> 而不是 <section>
不要把 <section> 元素作為一個(gè)普通的容器來(lái)使用,特別是當(dāng)<section>僅僅是為了美化樣式或方便腳本使用的時(shí)候,應(yīng)使用<div>
通俗來(lái)說(shuō)就是<article><section>更具有獨(dú)立性、完整性。可通過(guò)該段內(nèi)容脫離了所在的語(yǔ)境,是否完整、獨(dú)立來(lái)判斷。

頁(yè)面基本結(jié)構(gòu):


image.png

三、css 規(guī)范

使用BEM規(guī)范進(jìn)行css命名

BEM 規(guī)范

BEM 代表塊(Block),元素(Element),修飾符(Modifier)

編程方法論中一個(gè)最常見(jiàn)的例子就是面向?qū)ο缶幊蹋∣OP)。這一編程范例出現(xiàn)在許多語(yǔ)言中。在某種程度上,BEM 和 OOP 是相似的。

塊(Block)

使用 vue 進(jìn)行開(kāi)發(fā),一個(gè)組件就是一個(gè)Block

一個(gè)塊是一個(gè)獨(dú)立的實(shí)體,就像應(yīng)用的一塊“積木”。一個(gè)塊既可以是簡(jiǎn)單的也可以是復(fù)合的(包含其他塊)。

例如一個(gè)輸入域和一個(gè)按鈕是 Search 塊的中的元素。

image.png

元素(Element)

一個(gè)元素是塊的一部分,具有某種功能。元素是依賴上下文的:它們只有處于他們應(yīng)該屬于的塊的上下文中時(shí)才是有意義的。

例如一個(gè)輸入域和一個(gè)按鈕是 Search 塊的中的元素。

image.png

用塊與元素來(lái)描述頁(yè)面

頁(yè)面的內(nèi)容由塊和元素構(gòu)成,每個(gè)塊都可以看成一個(gè)組件,部分公用性比較強(qiáng)的元素,也可以看成一個(gè)組件。

一個(gè)復(fù)雜塊里有可能再嵌套多個(gè)單一功能塊。

例如,一個(gè) Head 塊會(huì)包含其他塊:

image.png

每一個(gè)塊和元素,都應(yīng)該有對(duì)應(yīng)的關(guān)鍵字。

用來(lái)標(biāo)識(shí)一個(gè)具體塊的關(guān)鍵字其實(shí)就是這個(gè)塊的名字(block name)。

例如,menu可以作為Menu塊的關(guān)鍵字,head可以作為Head塊的關(guān)鍵字。

例如,菜單中的每個(gè)菜單項(xiàng)就是menu塊的item元素。

一個(gè)塊范圍內(nèi)的一種元素的名字也必須是唯一的。一種元素可以重復(fù)出現(xiàn)多次。

例如上面的 head 塊,可以這樣分解

<block:xxx>表示一個(gè)塊,<element:column>表示一個(gè)元素

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

這種結(jié)構(gòu)可以叫做 BEM 樹(shù)(和 DOM 樹(shù)類似)。

塊的獨(dú)立性

一個(gè)獨(dú)立的塊等于一個(gè)獨(dú)立的組件,可以放置在頁(yè)面的任意位置 ,包括嵌套在其他塊里。

使用 BEM 規(guī)范來(lái)命名 CSS

獨(dú)立的 css

從 CSS 的角度來(lái)看:

  • 一個(gè)塊(或者一個(gè)元素)必須有一個(gè)唯一的“名字”(一個(gè) CSS 類)這樣才能被 CSS 規(guī)則所作用。
  • HTML 元素不能用作 CSS 選擇器(如.menu td)因?yàn)檫@樣的選擇器并非是完全上下文無(wú)關(guān)的。
  • 避免使用級(jí)聯(lián)(cascading)選擇器(注:如.menu .item)。

下面是一種可能的 CSS 類命名方案:

一個(gè)元素的 CSS 類名是一個(gè)塊名和一個(gè)元素名的組合,它們中間用一些符號(hào)隔開(kāi)。

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

一個(gè)相對(duì)復(fù)雜的例子

下面是根據(jù)之前的 header 例子創(chuàng)建的一個(gè)對(duì)應(yīng)的 html 結(jié)構(gòu)

1、根據(jù)頁(yè)面結(jié)構(gòu)生成 bem 樹(shù)結(jié)構(gòu)
<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、轉(zhuǎn)換成對(duì)應(yīng)的 dom 樹(shù)以及對(duì)應(yīng)的 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 組件進(jìn)行分解
<!-- 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、文件結(jié)構(gòu)
components/
|- home-header/
|-- HomeHeader.vue
|-- HeaderMenu.vue
|-- HeaderLogo.vue
|-- HeaderSearch.vue
|-- HeaderAuth.vue
|- HomeMain.vue
|- HomeFooter.vue
pages/
|- PageHome.vue

四、js 規(guī)范

1、使用 prettier 來(lái)規(guī)范 jscss 代碼格式

  • vscode 的插件中搜索prettier,進(jìn)行安裝
  • 在文件-首選項(xiàng)-設(shè)置中,將以下配置加到 User Settings 配置文件
"editor.formatOnSave": true
"prettier.singleQuote": true,
"prettier.semi": false

2、js 代碼規(guī)范

2.1 變量

  • 命名方式:小駝峰
  • 命名規(guī)范:前綴名詞
  • 命名建議:語(yǔ)義化

變量聲明要根據(jù)上下文環(huán)境語(yǔ)義化聲明,不能使用無(wú)任何語(yǔ)義的關(guān)鍵詞或者數(shù)字來(lái)表示變量

除了一些約定俗成的簡(jiǎn)寫,正常情況下盡量不使用簡(jiǎn)寫聲明變量

需要做到看到變量名稱,就知道這個(gè)變量是用來(lái)做什么的

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

  • 命名方式:全部大寫
  • 命名規(guī)范:使用大寫字母和下劃線來(lái)組合命名,下劃線用以分割單詞
  • 命名建議:語(yǔ)義化

案例

// 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 函數(shù)

命名規(guī)范
  • 命名方式:小駝峰式命名法。
  • 命名規(guī)范:前綴應(yīng)當(dāng)為動(dòng)詞。
  • 命名建議:語(yǔ)義化。

可以參考如下的動(dòng)作:

  • has: 判斷是否含有某個(gè)值
  • is: 判斷是否為某個(gè)值
  • get: 獲取某個(gè)值
  • set: 設(shè)置某個(gè)值
  • update: 更新某個(gè)值
  • fetch: ajax 請(qǐng)求(一般用在 vuex 里的 actions
  • on: 觸發(fā)事件(click/changedom 事件或者emit派發(fā)事件)
  • render: 渲染頁(yè)面
  • handle: 執(zhí)行某一個(gè)事件(如果不清楚用什么動(dòng)詞前綴,可以使用 handle

還有很多類似的動(dòng)作,例如:add/delete/put/select/change/move/remove/to

案例:

// 接口請(qǐng)求
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
  }
}

// 觸發(fā)某個(gè)dom事件
function onUserIptBlur(e) {
  let username = e.target.value
  this.changeUserName(username)
}

function changeUserName(username) {
  this.username = username
}

// 渲染登錄浮層
function renderLoginModel() {
  this.loginModelVisible = true
}

控制函數(shù)的副作用

setupdate等動(dòng)詞前綴的方法,一般用來(lái)修改某個(gè)全局變量,有一定副作用,除了修改 vuex 修改狀態(tài)使用,其它情況建議使用get返回一個(gè)新的修改后的對(duì)象,然后在handle方法中修改該全局變量

不要修改函數(shù)的入?yún)?/p>

案例:

// 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等動(dòng)詞前綴
mutations = {
  updateUserInfo(state, userInfo) {
    state.userInfo = userInfo
  }
}

actions = {
  setUserInfo({ commit }, data) {
    apis.fetchUserInfo(data).then(res => {
      let userInfo = res.result
      commit('updateUserInfo', userInfo)
    })
  }
}

// 如果確實(shí)需要修改數(shù)據(jù),可以走vuex數(shù)據(jù)流
...mapMutations(['updateUserInfo'])
function changeUserRole(user) {
  let roleUser = this.getRoleUser(user)
  this.updateUserInfo(roleUser)
}

// 或者
function changeUserRole(user) {
  this.roleUser = this.getRoleUser(user)
}

無(wú)副作用的函數(shù),是不依賴上下文,也不改變上下文的函數(shù)

案例:

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

最小函數(shù)準(zhǔn)則

一個(gè)函數(shù)只做一件事情,提高代碼可維護(hù)性和模塊化

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

遵循一個(gè)函數(shù)只做一件事的原則,我們可以將上述功能拆成兩個(gè)函數(shù)fetchMultipleUserfetchSingleUser 來(lái)實(shí)現(xiàn)。在需要獲取用戶數(shù)據(jù)時(shí),只需要選擇調(diào)用其中的一個(gè)函數(shù)。

async function fetchMultipleUser(idList) {
  return await request.post('/api/users/', { idList })
}

async function fetchSingleUser(id) {
  return await fetchMultipleUser([id])[0]
}

上述改良不僅改善了代碼的可讀性,也改善了可維護(hù)性。舉個(gè)例子,如果后期需要去除單一查詢功能,按照未改良前的邏輯,需要在函數(shù)內(nèi)部進(jìn)行變動(dòng),而且很擔(dān)心會(huì)有其它問(wèn)題。按照改良后的版本,直接去掉fetchSingleUser方法就行

五、組件規(guī)范

每個(gè) Vue 組件的代碼建議不要超出 200 行,如果超出建議拆分組件。

組件一般情況下是可以拆成基礎(chǔ)/ui 部分和業(yè)務(wù)部分,基礎(chǔ)組件一般是承載呈現(xiàn),基礎(chǔ)功能,不和業(yè)務(wù)耦合部分。

業(yè)務(wù)組件一般包含業(yè)務(wù)功能業(yè)務(wù)特殊數(shù)據(jù)等等。

組件規(guī)范

1、UI 組件/基礎(chǔ)組件

放在src/components

UI 組件可以是某個(gè)頁(yè)面的一塊block,和業(yè)務(wù)關(guān)聯(lián)性較強(qiáng),數(shù)據(jù)由容器組件通過(guò) props 傳給 ui 組件,容器組件由 ui 組件組成
基礎(chǔ)組件可以是公共組件,業(yè)務(wù)性較弱,通用性強(qiáng),可以包含一些公共 mixin

參考目錄結(jié)構(gòu):

components/
|- base/
|-- BaseDialog.vue
|-- BaseToast.vue
|- home-header/
|-- HomeHeader.vue
|-- HeaderMenu.vue
|- HomeMain.vue
|- HomeFooter.vue

2、容器組件

放在src/pages

和當(dāng)前業(yè)務(wù)耦合性比較高,由多個(gè)基礎(chǔ)組件組成,可承載當(dāng)前頁(yè)的業(yè)務(wù)接口請(qǐng)求和數(shù)據(jù)(vuex)。
容器組件獲取vuex相關(guān)狀態(tài),通過(guò)props傳遞數(shù)據(jù)給 ui 組件或者基礎(chǔ)組件,通過(guò)$emit來(lái)獲取子組件分發(fā)的數(shù)據(jù)

參考目錄結(jié)構(gòu):

pages/
|- PageHome.vue

組件開(kāi)發(fā)風(fēng)格

組件名為多個(gè)單詞

組件名應(yīng)該始終是多個(gè)單詞的,根組件 App 除外。

這樣做可以避免跟現(xiàn)有的以及未來(lái)的 HTML 元素相沖突,因?yàn)樗械?HTML 元素名稱都是單個(gè)單詞的。

// bad
Vue.component('todo', {
  // ...
})
export default {
  name: 'Todo',
  // ...
}

// good
Vue.component('todo-item', {
  // ...
})
export default {
  name: 'TodoItem',
  // ...
}

組件命名的大小寫

單文件組件的文件名應(yīng)該要么始終是單詞大寫開(kāi)頭 (PascalCase),要么始終是橫線連接 (kebab-case)。

組件文件命名建議統(tǒng)一PascalCase

// bad
components/
|- mycomponent.vue

components/
|- myComponent.vue

// good
components/
|- MyComponent.vue

components/
|- my-component.vue

組件引用的大小寫

js/vue 文件內(nèi)部的組件名可以是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>

基礎(chǔ)組件名用特定前綴開(kāi)頭

應(yīng)用特定樣式和約定的基礎(chǔ)組件 (也就是展示類的、無(wú)邏輯的或無(wú)狀態(tài)的組件) 應(yīng)該全部以一個(gè)特定的前綴開(kāi)頭,比如 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 前綴

只應(yīng)該擁有單個(gè)活躍實(shí)例的組件應(yīng)該以 The 前綴命名,以示其唯一性。

這不意味著組件只可用于一個(gè)單頁(yè)面,而是每個(gè)頁(yè)面只使用一次。這些組件永遠(yuǎn)不接受任何 prop,因?yàn)樗鼈兪菫槟愕膽?yīng)用定制的,而不是它們?cè)谀愕膽?yīng)用中的上下文。如果你發(fā)現(xiàn)有必要添加 prop,那就表明這實(shí)際上是一個(gè)可復(fù)用的組件,只是目前在每個(gè)頁(yè)面里只使用一次。

// bad
components/
|- Heading.vue
|- MySidebar.vue

// good
components/
|- TheHeading.vue
|- TheSidebar.vue

緊密耦合的組件名

和父組件緊密耦合的子組件應(yīng)該以父組件名作為前綴命名。

如果一個(gè)組件只在某個(gè)父組件的場(chǎng)景下有意義,這層關(guān)系應(yīng)該體現(xiàn)在其名字上。因?yàn)榫庉嬈魍ǔ?huì)按字母順序組織文件,所以這樣做可以把相關(guān)聯(lián)的文件排在一起。

// 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容