你应该知道的前端命名规范

8,136 阅读8分钟

前端命名规范

命名规则

PascalCase: 各个单次首字母大写

camelCase:首个单词首字母小写,其余单词首字母大写

原则:命名对可读性很重要,英文单次要尽量准确

项目命名

  • 文件命名不得含有空格
  • 名称使用小写字母,不得含有大写字母
  • 名称较长时采用半角连接符(-, _)分隔

good:

baidu/fe-team/project-athena

className命名

className名称要能够反映元素的作用和意图,避免使用晦涩难懂的名称

good:

.header {
    font-weight: 600;
    color: #000;
}

not good:

.fw-600 {
    font-weight: 500;
}

.gray {
    color: #ccc
}

常量

单词字母大写,单词之间通过下划线连接

good:

const MAX_IMAGE_SIZE = 10 * 1024 * 1024;

not good:

const MaxImageSize = 10 * 1024 * 1024;
const maximagesize = 10 * 1024 * 1024;
const maxImageSize = 10 * 1024 * 1024;

变量

采用camelCase的命名方式,根据不同数据类型和单复数命名:

  • 普通变量(number, string, date)
  • 布尔类型:需要一个标识变量含义的前缀,比如has, is, wether, can, should
  • 数组/集合等复数形式:最好以slist等能够标识复数形式的后缀结尾,标识当前变量是复数形式,提高可读性

good:

let isVisited = false;

not good:

let visited = false;

函数

采用camelCase的命名方式,以动词+宾语(动宾短语)的方式命名

动词表示当前动作,比如query查询数据,send发送数据,宾语代表当前获取的目标对象,比如queryUsers获取用户集合,saveUserInfo保存用户信息

good:

// 获取商品列表
queryProductList() {
    // TODO
}

not good:

productList() {
    // TODO
}

类的成员

  • 公共属性:同变量命名方式
  • 公共方法:同函数命名方式(动宾短语)
  • 私有属性和方法:分别同公共属性和方法的命名规则,不过前缀为_
function User(name) {
    var _name = name; // 私有变量
    
    // 公共方法
    this.getName = function() {
        return _name;
    }
}
const user = new User('lucy');
user.setName('cherry');
const username = user.getName(); // => cherry, 输出_name的值

React项目结构化和组件命名

  • 目标对象:类(class)和通过const定义的组件
  • 命名原则:名称清晰且唯一,容易查找,而且避免歧义

目录结构

一个完整的项目会包括src, public, README.md, .ignore, package.json, yarn.lock等文件夹和文件,src是存放源代码的文件夹,本文聚焦在src文件夹下的目录结构。

src
   ├── componnets
   └── containers

如上图所示,src下有componentscontainers两个目录,这样存在以下几个问题:

  1. 主观规则: componnetscontainers职责不分明,团队成员对组件和容器的理解存在主观差异,开发者难以一致性地赞成和评判这两个目录
  2. 没有考虑组件的动态变化: 即使定义了某个特定类型的组件,在项目生命周期内也有可能发生变化,因此不得不将组件从componnetscontainers之间来回移动
  3. 组件同名: 组件在项目中的命名应该是声明式且唯一的,从而避免对组件的职责产生困惑。但上面的命名方式为组件同名提供了可能:一个可以是容器,一个可以是展示型组件
  4. 效率低下: 即使实现的是单一功能,也不得不在componnetscontainers目录之间不停来回切换,因为一个功能同时包含容器类型和展示型组件太常见了

有一种基于以上方式的变种:

── src
   └── User
       ├── componnets
       └── containers

这种方式可以避免在遥远的componnetscontainers目录之间来回切换的问题,但是当项目中的模块较多时,那么项目中会创建几十个componnetscontainers目录,甚至更多。

拆分和组合代码

components目录下,通过模块/功能(model/feature)的结构来组织文件。

在对用户进行增删改查时,只需要一个用户(User)模块,所以目录结构会像这样:

──src
    └── components
        └── User
            ├── Form.jsx
            └── List.jsx

当一个组件有不止一个文件的时候,我们会将这个组件和它对应的文件放在同一个文件夹下,并且使用同一个名字来命名,比如现在有一个组件Form.jsx,引用了Form.css中的样式,那目录结构就会像这样:

src
└──components
    └── User
        ├── Form
        │   ├── Form.css
        │   └── Form.jsx
        └── List.jsx

UI组件目录

除了模块划分,我们还会在components目录下创建一个UI目录,用于存放公共组件。UI目录下的组件不属于任何一个业务模块,它们应该可以直接放到公共开源库中,比如Button, Select, Modal, Input, Checkbox以及可视化组件等。

存放公共组件的目录也可以命名为common

组件命名

以上讲的是如何通过模块/功能(module/feature)来组织目录结构,但并未将如何对目录命名。

这里针对的对象是类class和通过const定义的组件:

class MyComponent extends Component {}
const MyComponent = () => {};

正如上文提到的,组件的命名在整个应用中应该是清晰且唯一的,这样可以查找方便而且避免带来歧义。

当应用程序出现运行时错误或使用React开发者工具调试时,组件的名字非常易用,因为错误发生的地方总伴随着组件的名字。

关于组件的命名,可以采用基于路径的命名方式(*path-based-component-naming*),即根据组件到其所在目录命名,如果组件在components目录之外,就根据组件到src的相对路径命名。比如位于components/User/List.jsx路径的组件会被命名为UserList

定义组件的文件与所在文件夹名字同名时,在对组件命名时不必进行重复。比如说components/User/Form/Form.jsx应该被命名为UserForm而非UserFormForm.

采用以上原则命名会有几个好处:

  1. 便于搜索: 如果你的编辑器支持模糊搜索,那么搜索UserForm就可以找到对应的文件
  1. 在目录树中寻找文件,也可以很快定位到
  1. 可以避免在引入时重复名称 遵循path-based-component-naming的命名规则可以根据组件上下文来命名文件。以上面的Form组件为例,已知它是User模块下的Form组件,就没有必要在Form组件的文件名上重复User这个单词,使用Form.jsx就可以了。

在项目中使用完整的名字来命名文件,会导致相同的部分多次重复,同时引入的路径太长。下面的例子可以看出区别:

import ScreenUserForm from './screens/User/UserForm';
// vs
import ScreenUserForm from './screens/User/Form';

单个例子看不出明显的优势,但是应用复杂度上升一点就可以看到很大的区别:

import MediaPlanViewChannel from './MediaPlan/MediaPlanView/MediaPlanViewChannel.jsx';
// vs
import MediaPlanViewChannel from './MediaPlan/View/Channel.jsx';

现在想象一下一个文件名重复五到十次。

基于以上原因,根据组件的上下文环境以及组件的相对路径命名是更为可取的方式。

页面(Screen)

如果要对一个用户做增删改查操作,需要有用户列表页面、创建用户页面以及编辑已有用户页面。

在应用中,通过组件的组合就构成一个页面。理想状态下,页面不应包含任何逻辑,而仅仅是一个函数式组件。

src目录为根目录,将不同页面分散在不同目录中,因为页面划分是根据路由定义而非模块划分。

── src
│   ├── components
│   └── screens
│       └── User
│           ├── Form.jsx
│           └── List.jsx

假设项目中使用react-router,在screens目录下放置Root.jsx文件并在其中定义路由,那么Root.jsx的代码可能像下面这样:

 import React, {Component} from 'react';
import {Router} from 'react-router';
import {Redirect, Route, Switch} from 'react-router-dom';

import ScreensUserForm from './User/Form';
import ScreensUserList from './User/List';

const ScreensRoot = () => (
  <Router>
    <Switch>
      <Route path="/user/list" component={ScreensUserList} />
      <Route path="/user/create" component={ScreensUserForm} />
      <Route path="/user/:id" component={ScreensUserForm} />
    </Switch>
  </Router>
);

export default ScreensRoot;

将所有页面放在与路由同名的同一个目录下,user/ -> User/。为每一个父级路由建立一个目录,在这个目录中组织子路由。示例中,创建了User目录并且将List页面和Form页面放在里面,这种方式可以轻松地根据url定位当前路由渲染的页面。

可以发现路由文件中引入的组件都以Screen为前缀。当在components目录之外创建组件时,应该以相对src的目录命名。位于src/screens/User/List.jsx路径的组件应该命名为ScreensUserList.

创建了Root.jsx之后,src目录结构如下:

src
│   ├── components
│   │   ├── UI
│   │   └── User
│   │       ├── Form
│   │       │   ├── Form.css
│   │       │   └── Form.jsx
│   │       └── List
│   └── screens
│       ├── Root.jsx
│       └── User
│           ├── Form.jsx
│           └── List.jsx

实践经验

  • components、container两个文件夹用于保存普通组件与容器组件,容易导致含义混淆,团队协作中对名称的理解不一致引起效率低下以及查找组件困难的问题
  • 由多个子组件构成的组件,在存放时应该保存在同一个文件夹中
  • components中应该有个UI文件夹,用于存放与模块无关的公共组件,比如Buttons, Inputs, Checkboxes, Selects, Modals...

参考链接

凹凸实验室代码规范

腾讯alloyteam代码规范

百度FEX代码规范

CodeReview最佳实践

前端开发规范:命名规范、html规范、css规范、js规范

React项目结构化和组件命名