经过实践应用,用合适的方案来给目录、文件等命名将让程序开发变得顺畅。
前言
阅读本文前先了解以下两篇:
- 《程序开发时字母大小写的命名规范(目录名、文件名、函数名、变量名、数据库字段等,小驼峰/大驼峰/短横线/下划线)》
- 《程序开发时单数复数及前缀的命名规范(目录名、文件名、函数名、变量名、数据库字段等)》
根据这两篇的分析及开发经验订立以下命名方案:
上菜
先把最终的样子拿出来看,后面的具体解释根据这份清单来。
router/
├── index.ts
csdn/
├── components/
├── config/
│ ├── index.ts
│ ├── menu.json
│ └── system.json
├── services/
│ ├── index.ts
│ ├── httpClient.ts
│ ├── serviceAuth.ts
├── stores/
│ ├── index.ts
│ ├── pinia.ts
│ ├── storeAuth.ts
│ ├── storeMenu.ts
│ └── storeTab.ts
├── types/
│ ├── index.d.ts
│ ├── typeAuth.d.ts
│ ├── typeMenu.d.ts
│ └── typeService.d.ts
├── utils/
│ ├── index.ts
│ ├── utilTab.ts
│ └── crypto/
│ ├── index.ts
│ ├── utilBase64.ts
│ ├── utilRsa.ts
│ └── utilSecure.ts
└── views/
├── _system/
│ ├── auth/
│ │ ├── ForgotPassword.vue
│ │ ├── Login.vue
│ │ └── Register.vue
│ ├── workspace/
│ │ ├── Layout.vue
│ │ ├── LeftMenu.vue
│ │ ├── MainContent.vue
│ │ ├── MenuItem.vue
│ │ └── TabContainer.vue
│ │ └── TopNav.vue
│ └── About.vue
└── home
├── Dashboard.vue
└── Notice.vue
规范
- 一层目录除了
config
,其它都是复数。- 原因是
config
在使用时将被拼接成一个整体使用,如果每个config
单独调用,那么考虑重立名字。
- 原因是
- 非vue的文件名开头以该目录单数命名。
- 比如
utils/utilTab
、stores/storeTab
本来并不在文件名开头写目录单数,但是开发时遇到这个情况
三个名字一模一样,分别属于路由、控制器和模型,这里目录名还能看到不一样,可如果目录名也一样呢?
改成带有目录名前缀后,三个不同的menu
同时出现在编辑器标签页里也能清晰分辨作用。
- 比如
当使用了目录单数名前缀后,目录的复数名是不是也就顺理成章了?
utils/utilMenu.ts
、utils/utilTab
,这样的目录/文件
看着顺眼不?
- 一般情况内容都包含在命名空间中,且空间名称与文件名相同。
- 比如
utilMenu.ts
中的命名空间就是utilMenu
,这样的好处是在其它文件中引用该文件后直接就调用该命名空间了。
看下面这个例子,不用说什么,单单看到utilMenu.action()
,立刻就知道在哪个目录下,起什么作用。
- 比如
import { utilMenu } from "../utils/utilMenu";
const csdn = utilMenu.handler;
实际开发时因为在index.ts
中已经引入了该组件,所以这么写就更舒服了:
import { utilMenu } from "../utils";
const csdn = utilMenu.handler;
- 方法、变量等命名规范参考之前的分析。
- 这里多讲一点,每个命名空间里的类名可以不太在意,但是其对应的实例如果要
命名导出
一般用use前缀,例如下面的代码:
- 这里多讲一点,每个命名空间里的类名可以不太在意,但是其对应的实例如果要
// ./stores/storeMenu.ts
import { ref } from 'vue';
import { defineStore } from 'pinia';
// 命名空间与文件名相同
namespace storeMenu {
// 标识随意,本实例名规范参考之后的说明,主要是builder或factory的区别
export const builder = defineStore('csdnMenuStore', () => {
const isCollapsed = ref(false);
const toggleCollapse = () => {
isCollapsed.value = !isCollapsed.value;
};
return {
isCollapsed,
toggleCollapse
};
});
}
// 默认导出有且只有一个
export default storeMenu.builder;
// 命名导出中必须有命名空间
export { storeMenu };
// 命名导出中必须有一个实例,且该实例名用use做前缀,目录单数做后缀
export const useMenuStore = storeMenu.builder;
// ../utils/crypto/utilSecure.ts
import SecureLS from 'secure-ls';
// 命名空间与文件名相同
namespace utilSecure {
// 类名随意
class SecureStorage {
private ls: SecureLS;
constructor() {
this.ls = new SecureLS({
isCompression: false,
encryptionSecret: 'csdn'
});
}
// 方法名随意
setItem(key: string, value: string): void {
this.ls.set(key, value);
}
// 方法名随意
getItem(key: string): string | null {
return this.ls.get(key);
}
}
// 本实例名规范参考之后的说明,主要是builder或factory的区别
export const builder = new SecureStorage();
}
// 默认导出有且只有一个
export default utilSecure.builder;
// 命名导出中必须有命名空间
export { utilSecure };
// 命名导出中必须有一个实例,且该实例名用use做前缀,目录单数做后缀
export const useSecureUtil = utilSecure.builder;
这么规范的好处你们在开发的时候自己体会。
- 实例名
上面的代码案例中已经涉及到builder
,看一下便明白本段在说啥。
除了接口类文件,一般命名空间中都有一个实例,无论这个实例是由类new
来的又或者天生就是,只要这个实例是对外暴露的,那么也要有个标准名称,比如说可以根据返回值是实例还是函数定义来区分,订立原则如下:
// 返回实例,用builder
const su = storeUser.builder();
const { getId, toggleId } = su;
su.getId();
storeUser.builder().getId();
// 返回函数定义用factory
const su = storeUser.factory();
const { getId, toggleId } = su();
su().getId();
storeUser.builder()().getId();
参考:
《factory、creator、handler、builder、provider、generator、instance、constructor之间在返回实例或返回函数定义上有什么区别》
- 总引入文件
- 每个目录下基本都有一个
index.ts
,将该目录下的所有文件引入其中,这样在写代码时只需要引入到该目录即可,不需要关心下面的目录文件。 - 推荐写法一的引入方法,扩展性更强。我个人喜欢写法一的文件调用,原因是:这么写的话,当你在任何地方看到他,都立刻能知道他从哪里来。
- 如果用的函数、属性少,就用下面案例一中写法一、二的使用方法,多就用写法三、四,用到哪里调哪里。
- 有了总引入文件后,哪怕套再多层,也能直击要害。
- 每个目录下基本都有一个
案例一
// ./stores/index.ts
export * from './pinia';
export * from './storeAuth';
export * from './storeMenu';
export * from './storeTab';
// 某文件
// 写法一:
import {storeMenu} from '../stores'
const { isCollapsed, toggleCollapse } = storeMenu.action();
// 写法二:
import { useMenuStore } from '../stores';
const { isCollapsed, toggleCollapse } = useMenuStore();
// 写法三:
import { useMenuStore } from '../stores/storeMenu';
const menuStore = useMenuStore();
menuStore.isCollapsed;
menuStore.toggleCollapse();
// 写法四:每个文件都有一个自己的默认导出,所以要用默认内容,导入的时候就必须精确到文件
import csdn from '../stores/storeMenu';
const menuStore = csdn();
menuStore.isCollapsed;
menuStore.toggleCollapse();
// 更多写法...
案例二
// ./utils/crypto/index.ts
// 所有命名导出继续导出
export * from './utilSecure';
export * from './utilBase64';
// 选择性的让命名导出继续命名导出
export { utilSecure, useSecureUtil } from './utilSecure';
export { utilBase64, useBase64Util } from './utilBase64';
//因为default默认的会重叠,所以不带默认的家伙们玩了
...
// 某文件
// 套娃只要引入一层,比如这里实际引用文件为 ../ytils/crypto/utilBase64.ts,但只要引入一层就能使用
// 这里导入命名空间
import { utilBase64 } from '../utils';
utilBase64.handler.encode('csdn');
//而因为在./utils/crypto/index.ts已经引入了命名导出,所以如下也是可以的
// 这里导入实例
import { useBase64Util } from '../utils';
useBase64Util.encode('csdn');
- 其它命名
比如./stores/pinia.ts
、./services/httpClient.ts
等因为作用比较特殊且具有全局性,所以不把他们的前缀强制取目录单数名。
具体情况
目录中的目录改用单复数?
在上面的套娃案例中utils/crypto/...
,这里的crypto
该用复数还是单数呢?分析说明如下:
分析:crypto
vs cryptos
当前的项目目录结构中:
csdn/
├── utils/
│ └── crypto/
│ ├── index.ts
│ ├── utilBase64.ts
│ ├── utilRsa.ts
│ └── utilSecure.ts
- 单数(即
crypto
),因为这个目录表示一个加密工具模块,而不是多个独立的工具集。crypto
更倾向于指一个整体的加密工具库。 - 如果这个目录内专门存放不同类型的加密工具且各自独立,也可以考虑用复数(
cryptos
),但大多数情况下,单数会显得更加自然和简洁。
最终决定:crypto
- 更符合模块命名的习惯,表明这是一个专门处理加密的工具集合。
- 为什么这个例子单独拿出来讲,因为这是提醒你(我),在遇到单个情况的时候,一定具体情况具体分析具体决定。
总结
总的规范使用本解决方案,特例单独使用。
觉不错请一键三连。
更新
2024.12.02 更新
项目接近尾声,根据目前的开发经验,修改或确定了如下两条:
- config改为configs
原本config全为json格式,直接在index.ts中合并即可。但实际使用中json文件不能标注释,无法在不同情况下提供不同配置,且效率还差点。所以综合考虑还是用回传统的方式,用export分别暴露。 - 非组件对外暴露都遵循如下格式
命名空间+主输出符,并都对应一个简写方式,比如
配置类configTest.options
对应getTestConfig
;
实例类serviceTest.builder
对应useTestService
;
定义类storeTest.factory
对应makeTestStore
。