前端开发规范

作者:taomas
链接:https://www.jianshu.com/p/58417e0ae693

一、命名规范

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 标签语义化

  • :定义文档或者文档的部分区域的页眉,应作为介绍内容或者导航链接栏的容器。
  • :定义文档的主要内容,该内容在文档中应当是独一无二的,不包含任何在文档中重复的内容,比如侧边栏,导航栏链接,版权信息,网站 logo,搜索框(除非搜索框作为文档的主要功能)。
  • :表示文档、页面、应用或网站中的独立结构,是可独立分配的、可复用的结构,如在发布中,它可能是论坛帖子、杂志或新闻文章、博客、用户提交的评论、交互式组件,或者其他独立的内容项目。
  • :定义最近一个章节内容或者根节点元素的页脚。一个页脚通常包含该章节作者、版权数据或者与文档相关的链接等信息,使用 footer 插入联系信息时,应在 footer 元素内使用
    元素。
  • :表示文档中的一个区域(或节),比如,内容中的一个专题组。

如果元素内容可以分为几个部分的话,应该使用

而不是

不要把
元素作为一个普通的容器来使用,特别是当
仅仅是为了美化样式或方便脚本使用的时候,应使用

通俗来说就是
更具有独立性、完整性。可通过该段内容脱离了所在的语境,是否完整、独立来判断。

页面基本结构:


image.png

三、css 规范

使用BEM规范进行css命名

BEM 规范

BEM 代表块(Block),元素(Element),修饰符(Modifier)

编程方法论中一个最常见的例子就是面向对象编程(OOP)。这一编程范例出现在许多语言中。在某种程度上,BEM 和 OOP 是相似的。

块(Block)

使用 vue 进行开发,一个组件就是一个Block

一个块是一个独立的实体,就像应用的一块“积木”。一个块既可以是简单的也可以是复合的(包含其他块)。

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

image.png

元素(Element)

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

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

image.png

用块与元素来描述页面

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

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

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

image.png

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

用来标识一个具体块的关键字其实就是这个块的名字(block name)。

例如,menu可以作为Menu块的关键字,head可以作为Head块的关键字。

例如,菜单中的每个菜单项就是menu块的item元素。

一个块范围内的一种元素的名字也必须是唯一的。一种元素可以重复出现多次。

例如上面的 head 块,可以这样分解

表示一个块,表示一个元素


  
    
    
      
    
    
      
        
        Search
      
    
    
    
    
  


这种结构可以叫做 BEM 树(和 DOM 树类似)。

块的独立性

一个独立的块等于一个独立的组件,可以放置在页面的任意位置 ,包括嵌套在其他块里。

使用 BEM 规范来命名 CSS

独立的 css

从 CSS 的角度来看:

  • 一个块(或者一个元素)必须有一个唯一的“名字”(一个 CSS 类)这样才能被 CSS 规则所作用。
  • HTML 元素不能用作 CSS 选择器(如.menu td)因为这样的选择器并非是完全上下文无关的。
  • 避免使用级联(cascading)选择器(注:如.menu .item)。

下面是一种可能的 CSS 类命名方案:

一个元素的 CSS 类名是一个块名和一个元素名的组合,它们中间用一些符号隔开。

  • block: menu
  • element: item
  • modifier: active




一个相对复杂的例子

下面是根据之前的 header 例子创建的一个对应的 html 结构

1、根据页面结构生成 bem 树结构

  
    
    
      
    
    
      
        
        Search
      
    
    
    
    
  


2、转换成对应的 dom 树以及对应的 class 命名
3、使用 vue 组件进行分解
























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

















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

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

你可能感兴趣的:(前端开发规范)