文件命名技巧
在工作中,很多开发团队都会有语义化命名的规范要求,严格的团队会有 Code Review 环节,使用这种无意义命名的代码将无法通过审查,在这种背景下,开发者可能会在命名上花费很多时间,这里分享一些常用技巧,节约在命名上的时间开销。
Vue 组件
在 Vue 项目里,会有放在 views 下的路由组件,有放在 components 目录下的公共组件,虽然都是以 .vue
为扩展名的 Vue 组件文件,但根据用途,它们其实并不相同,因此命名上也有不同的技巧。
路由组件
路由组件组件通常存放在 src/views 目录下,在命名上容易困惑的点应该是风格问题,开发者容易陷入是使用 camelCase 小驼峰还是 kebab-case 短横线风格,或者是 snake_case 下划线风格的选择困难。
一般情况下路由组件都是以单个名词或动词进行命名,例如个人资料页使用 profile
命名路由,路由的访问路径使用 /profile
,对应的路由组件使用 profile.vue
命名,下面是几个常见的例子。
// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
// 首页
// e.g. `https://example.com/`
{
path: '/',
name: 'home',
component: () => import('@views/home.vue'),
},
// 个人资料页
// e.g. `https://example.com/profile`
{
path: '/profile',
name: 'profile',
component: () => import('@views/profile.vue'),
},
// 登录页
// e.g. `https://example.com/login`
{
path: '/login',
name: 'login',
component: () => import('@views/login.vue'),
},
]
export default routes
如果是一些数据列表类的页面,使用名词复数,或者名词单数加上 -list
结尾的 kebab-case 短横线风格写法,推荐短横线风格是因为在 URL 的风格设计里更为常见。
像文章列表可以使用 articles
或者 article-list
,但同一个项目建议只使用其中一种方式,以保持整个项目的风格统一,下面是几个常见的例子。
// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
// 文章列表页
// 翻页逻辑是改变页码进行跳转,因此需要添加动态参数 `:page`
// 可以在组件内使用路由实例 `route.params.page` 拿到页码
// e.g. `https://example.com/articles/1`
{
path: '/articles/:page',
name: 'articles',
component: () => import('@views/articles.vue'),
},
// 通知列表页
// 翻页逻辑使用 AJAX 无刷翻页,这种情况则可以不配置页码参数
// e.g. `https://example.com/notifications`
{
path: '/notifications',
name: 'notifications',
component: () => import('@views/notifications.vue'),
},
]
export default routes
列表里的资源详情页,因为访问的时候通常会带上具体的 ID 以通过接口查询详情数据,这种情况下资源就继续使用单数,例如下面这个例子。
// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
// 文章详情页
// 可以在组件内使用路由实例 `route.params.id` 拿到文章 ID
// e.g. `https://example.com/article/1`
{
path: '/article/:id',
name: 'article',
component: () => import('@views/article.vue'),
},
]
export default routes
如果项目路由比较多,通常会对同一业务的路由增加文件夹归类,因此上面的文章列表和文章详情页,可以统一放到 article 目录下,使用 list
和 detail
区分是列表还是详情。
// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
// 文章相关的路由统一放在这里管理
{
path: '/article',
name: 'article',
// 这是一个配置了 `<router-view />` 标签的路由中转站组件
// 目的是使其可以渲染子路由
component: () => import('@cp/TransferStation.vue'),
// 由于父级路由没有内容,所以重定向至列表的第 1 页
// e.g. `https://example.com/article`
redirect: {
name: 'article-list',
params: {
page: 1,
},
},
children: [
// 文章列表页
// e.g. `https://example.com/article/list/1`
{
path: 'list/:page',
name: 'article-list',
component: () => import('@views/article/list.vue'),
},
// 文章详情页
// e.g. `https://example.com/article/detail/1`
{
path: 'detail/:id',
name: 'article-detail',
component: () => import('@views/article/detail.vue'),
},
],
},
]
export default routes
对于一些需要用多个单词才能描述的资源,可以使用 kebab-case 短横线风格命名,例如很常见的 “策划面对面” 这种栏目,在设置路由时,比较难用一个单词在 URL 里体现其含义,就需要使用这种多个单词的连接。
// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
// 面对面栏目
{
path: '/face-to-face',
name: 'face-to-face',
component: () => import('@views/face-to-face.vue'),
},
]
export default routes
这种情况如果需要使用文件夹管理多个路由,同样建议使用 kebab-case 短横线风格命名,例如上面这个 “策划面对面” 栏目,可能会归属于 “开发计划” 这个业务下,那么其父级文件夹就可以使用 development-plan
这样的短横线命名。
公共组件
公共组件组件通常存放在 src/components 目录下,也可以视不同的使用情况,在路由文件夹下创建属于当前路由的 components 目录,作为一个小范围共享的公共组件目录来管理,而 src/components 则只存放全局性质的公共组件。
本节最开始提到了路由组件和公共组件并不相同,虽然都是组件,但路由组件代表的是整个页面,而公共组件更多是作为一个页面上的某个可复用的部件,如果开发者写过 Flutter ,应该能够更深刻的理解到这里的公共组件更接近于 Widget 性质的小部件。
公共组件通常使用 PascalCase 帕斯卡命名法,也就是大驼峰,为什么不用小驼峰呢?
这是源于 Vue 官网的一个 组件名格式 命名推荐:
使用 PascalCase 作为组件名的注册格式,这是因为:
PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 中导入和注册组件都很容易,同时 IDE 也能提供较好的自动补全。
><PascalCase />
在模板中更明显地表明了这是一个 Vue 组件,而不是原生 HTML 元素。同时也能够将 Vue 组件和自定义元素( web components )区分开来。
而且实际使用 PascalCase 风格的编码过程中,在 VSCode 里可以得到不同颜色的高亮效果,这与 kebab-case 风格的 HTML 标签可以快速区分。
<template>
<!-- 普通的 HTML 标签 -->
<!-- 在笔者的 VSCode 风格里呈现为桃红色 -->
<div></div>
<!-- 大驼峰组件 -->
<!-- 在笔者的 VSCode 风格里呈现为绿色 -->
<PascalCase />
</template>
养成这种习惯还有一个好处,就是使用 UI 框架的时候,例如 Ant Design Vue 的 Select 组件 ,在其文档上演示的是全局安装的写法:
<template>
<a-select>
<a-select-option value="Hello">Hello</a-select-option>
</a-select>
</template>
而实际使用时,为了更好的配合构建工具进行 Tree Shaking 移除没有用到的组件,都是按需引入 UI 框架的组件,因此如果平时有养成习惯使用 PascalCase 命名,就可以很轻松的知道上面的 <a-select-option />
组件应该对应的是 <SelectOption />
,因此是这样按需导入:
import { Select, SelectOption } from 'ant-design-vue'
可以说, PascalCase 这个命名方式也是目前流行 UI 框架都在使用的命名规范。
TypeScript 文件
在 Vue 项目里,虽然 TypeScript 代码可以写在组件里,但由于很多功能实现是可以解耦并复用,所以经常会有专门的目录管理公共方法,这样做也可以避免在一个组件里写出一两千行代码从而导致维护成本提高。
libs 文件
笔者习惯将这些方法统一放到 src/libs 目录下,按照业务模块或者功能的相似度,以一个名词或者动词作为文件命名。
例如常用的正则表达式,可以归类到 regexp.ts 里。
// src/libs/regexp.ts
// 校验手机号格式
export function isMob(phoneNumber: number | string) {
// ...
}
// 校验电子邮箱格式
export function isEmail(email: string) {
// ...
}
// 校验网址格式
export function isUrl(url: string) {
// ...
}
// 校验身份证号码格式
export function isIdCard(idCardNumber: string) {
// ...
}
// 校验银行卡号码格式
export function isBankCard(bankCardNumber: string) {
// ...
}
统一使用命名导出,这样一个 TS 文件就像一个 npm 包一样,在使用的时候就可以从这个 “包” 里面导出各种要用到的方法直接使用,无需在组件里重复编写判断逻辑。
import { isMob, isEmail } from '@libs/regexp'
其他诸如常用到的短信验证 sms.ts 、登录逻辑 login.ts 、数据格式转换 format.ts 都可以像这样单独抽出来封装,这种与业务解耦的封装方式非常灵活,以后不同项目如果也有类似的需求,就可以直接拿过去复用了!
types 文件
对于经常用到的 TypeScript 类型,也可以抽离成公共文件,笔者习惯在 src/types 目录管理公共类型,统一使用 .ts
作为扩展名并在里面导出 TS 类型,而不使用 .d.ts
这个类型声明文件。
这样做的好处是在使用到相应类型时,可以通过 import type
显式导入,在后期的项目维护过程中,可以很明确的知道类型来自于哪里,并且更接近从 npm 包里导入类型使用的开发方式。
例如上文配置路由的例子里,就是从 Vue Router 里导入了路由的类型:
// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
// ...
]
export default routes
在 types 目录下,可以按照业务模块创建多个模块文件分别维护不同的 TS 类型,并统一在 index.ts 里导出:
src
└─types
│ # 入口文件
├─index.ts
│ # 管理不同业务的公共类型
├─user.ts
├─game.ts
└─news.ts
例如 game.ts 可以维护经常用到的游戏业务相关类型,其中为了避免和其他模块命名冲突,以及一眼可以看出是来自哪个业务的类型,可以统一使用业务模块的名称作为前缀。
// src/types/game.ts
// 游戏公司信息
export interface GameCompany {
// ...
}
// 游戏信息
export interface GameInfo {
id: number
name: string
gameCompany: GameCompany
// ...
}
将该模块的所有类型在 index.ts 里全部导出:
// src/types/index.ts
export * from './game'
在组件里就可以这样使用该类型:
// 可以从 `types` 里统一导入,而不必明确到 `types/game`
import type { GameInfo } from '@/types'
const game: GameInfo = {
id: 1,
name: 'Contra',
gameCompany: {},
}
console.log(game)
TS 类型都遵循 PascalCase 命名风格,方便和声明的变量作为区分,大部分情况下一看到 GameInfo
就知道是类型,而 gameInfo
则是一个变量。