一个好的目录结构能够让你的开发效率事半功倍,也能够让新人快速熟悉项目,在合适的地方找到想要找到的东西。
为了达到这个目的,我认为一个好的目录结构应该遵循两个原则 - 分层与分类。
首先分层与分类不是对立的,它们在目录组织中应该相辅相成。接下来我会用一个 简单的例子 来讲述它们之间的关系以及如何使用它们让你的目录结构更清晰。
directory-example
├─src
| ├─App.tsx
| ├─ui-components
| | ├─Header.tsx
| | ├─Table.tsx
| | ├─__tests__
| | | ├─Header.test.tsx
| | | └Table.test.tsx
| ├─types
| | └some-globle-types.ts
| ├─pages
| | ├─utils
| | | ├─some-utils-cannot-be-a-model.ts
| | | ├─__tests__
| | | | └some-utils-cannot-be-a-model.test.ts
| | ├─models
| | | ├─Book.ts
| | | ├─__tests__
| | | | └Book.test.ts
| | ├─book-list
| | | ├─PageBookList.tsx
| | | ├─models
| | | | ├─Books.ts
| | | | ├─__tests__
| | | | | └Books.test.ts
| | | ├─hooks
| | | | ├─usePageBookList.ts
| | | | ├─__tests__
| | | | | └usePageBookList.test.ts
| | | ├─constants
| | | | └some-reuseable-constants.ts
| | | ├─components
| | | | ├─BookCard.tsx
| | | | ├─hooks
| | | | | ├─useBookCard.ts
| | | | | ├─__tests__
| | | | | | └useBookCard.test.ts
| | | | ├─__tests__
| | | | | └BookCard.test.tsx
| | | ├─__tests__
| | | | └PageBookList.test.tsx
| | ├─book
| | | ├─PageBook.tsx
| | | ├─hooks
| | | | ├─usePageBook.ts
| | | | ├─__tests__
| | | | | └usePageBook.test.ts
| | | ├─constants
| | | | └some-reuseable-constants.ts
| | | ├─components
| | | | ├─BookDescription.tsx
| | | | ├─__tests__
| | | | | └BookDescription.test.tsx
| | | ├─__tests__
| | | | └PageBook.test.tsx
| ├─modules
| | ├─utils
| | | └.gitkeep
| | ├─request
| | | └.gitkeep
| | ├─feature-toggle
| | | └.gitkeep
| | ├─authorization
| | | └.gitkeep
| | ├─authentication
| | | └.gitkeep
首先是我们项目中的主体 pages
文件夹。在这个文件夹下我们的原则是每一个单独的 route path
作为一个单独的文件夹,文件夹的名字与 route path
保持一致,以 route path
来做页面的分类,并且在当前文件夹下以一个 PageXxx
的页面文件作为这个 route path
的入口。
pages
├─book-list
| ├─PageBookList.tsx
| |
├─book
| ├─PageBook.tsx
| |
这样做的好处是不论谁想找到哪一个页面,都可以轻易通过页面和 url 的对应关系快速找到这个 url 下的入口页面。
然后在当前文件下下,这个页面可能被拆分成不同的组件,于是有一个 component
文件夹来存放这些被拆分出来的组件。同理页面中用到的一个或多个组件将被存放在同级目录的 hooks
文件夹下。之前提到的业务建模将被存放于 models
目录下... 而每一个文件的测试将存放于该文件同级目录下的 __tests__
目录下。
book-list
├─PageBookList.tsx
├─models
| ├─Books.ts
| ├─__tests__
| | └Books.test.ts
├─hooks
| ├─usePageBookList.ts
| ├─__tests__
| | └usePageBookList.test.ts
├─constants
| └some-reuseable-constants.ts
├─components
| ├─BookCard.tsx
| ├─hooks
| | ├─useBookCard.ts
| | ├─__tests__
| | | └useBookCard.test.ts
| ├─__tests__
| | └BookCard.test.tsx
├─__tests__
| └PageBookList.test.tsx
同时我们不提倡页面间的相互引用,页面间如果出现了相互引用,那么也就意味着这个组件/方法/模型是一个多个页面可以共同使用的公共组件/方法/模型。我们会将它向上级目录“提取”,也就是分层。
这样做是为了让修改代码的人认识到,你正在修改的是一个不仅仅你在使用的东西,你需要保证自己的修改不破坏其他页面的功能。
在 pages
这个文件夹下还有可能产生一些其他的文件夹,比如 types
、constants
等。在不同的项目会有不同的叫法,只不过大原则不变 - 以 route path
的页面作为分类,每一个类别中的东西大体相同,然后以引用者进行分层,自己的东西自己管理,大家的东西由上层管理。
pages
├─utils
| ├─some-utils-cannot-be-a-model.ts
| ├─__tests__
| | └some-utils-cannot-be-a-model.test.ts
├─models
| ├─Book.ts
| ├─__tests__
| | └Book.test.ts
├─book-list
├─book
|
聊完了 pages
目录,我们来看看大部分项目都会有的一些东西,首先是 src/ui-components
目录。如果已经理解了我们当前目录结构的思想,那么很显然这里的组件将会是全局通用的组件,这里的组件不应该包含任何业务逻辑。
那么为什么这些组件不遵循分类原则,由各个页面自己管理呢?
理论上每个项目都会有自己的 UI 设计,好的 UX team 还应该有自己的 design system。而 UI 组件就是这些设计的最终呈现,将它们维护在顶层符合“这是整个系统层面的设计实现”这一理念。既方便了管理和改动,又能够快速查看我们的组件哪些是已经有了的,哪些是还没有实现的。
更进一步,如果项目 B 也想使用同样的设计语言,这样的管理方式能够快速将其作为一个通用组件库剥离项目 A 将其应用的其它地方。
src
├─ui-components
| ├─Header.tsx
| ├─Table.tsx
| ├─__tests__
| | ├─Header.test.tsx
| | └Table.test.tsx
|
而 src/modules
下面则是全局通用的一些功能性方法,比如 request 的一些 client、config,比如用户的鉴权,比如项目中需要的一些功能开关等等。
src
├─modules
| ├─utils
| | └.gitkeep
| ├─request
| | └.gitkeep
| ├─feature-toggle
| | └.gitkeep
| ├─authorization
| | └.gitkeep
| ├─authentication
| | └.gitkeep
到这里我们的例子就结束了,我并没有事无巨细地列出每一个文件夹,只是拿出一些典型的文件夹来解释我的目录结构的思想,如果大家需要的话,我可以在日后的更新中慢慢完善这个目录结构。