软件系统变得复杂的三个成因是规模、结构与变化。javascript
针对三个成因,咱们能够经过如下方式简化系统:前端
依据这些原则,咱们该如何组织文件结构,管理好前端项目复杂度?java
『按文件类型组织』,这也是前端项目中最广泛的组织方式。例如:react
src
├── index.js
├── components
│ ├── index.js
│ ├── footer
│ ├── header
│ ├── posts
│ └── users
│ ├── UserDetail.js
│ └── UserList.js
├── containers
│ ├── home
│ ├── posts
│ │ └── index.js
│ └── users
│ └── index.js
├── actions
│ ├── posts.js
│ └── users.js
├── reducers
│ ├── posts.js
│ └── users.js
└── sagas
├── posts.js
└── users.js
复制代码
『按功能特性组织』。例如:git
src
├── components
│ ├── footer
│ ├── header
│ └── index.js
├── features
│ ├── posts
│ │ ├── action.js
│ │ ├── components
│ │ │ └── index.js
│ │ ├── containers
│ │ │ └── index.js
│ │ ├── reducers.js
│ │ └── sagas.js
│ └── users
│ ├── action.js
│ ├── components
│ │ └── index.js
│ ├── containers
│ │ └── index.js
│ ├── reducers.js
│ └── sagas.js
└── index.js
复制代码
两种文件组织方式都是在作『关注点分离』,不一样的是对『关注点』的理解。github
『语言』分离和『组件』分离 json
『功能特性』分离redux
File-Type First 的特色是:简洁、直接,符合开发者的直觉。而 Feature First 在管理大规模项目的复杂度上更有优点。前端工程师
Feature First 文件组织方式的优点:app
代码易于查找、定位 代码的组织方式反映了产品结构,与产品需求相对应。
代码更易于维护 每一个 Feature 隔离,当修改一个 Feature 中的代码修改、重构时,不会影响其它 Feature。 多特性并行开发时,更大程度上避免 merge 时产生的冲突。
启用 Feature Flags 机制
Feature Flag 是一种经过配置开功能特性的技术,无须从新部署代码。
像相似 A/B Testing 的需求,也能够借用 Feature Flags 实现。
代码示例:
// features.json
{
...,
portal: true,
users: true,
posts: false
}
// index.js
export function isFeatureEnabled(feature) {
return features[feature] || false;
}
复制代码
若是某个 Feature 比较复杂,能够将其进行一步细分,造成 Feature 的嵌套结构。 例如:将 features/users 细分为
features/users/features/detailView
features/users/features/listView
src
├── features
│ └── users
│ ├── components
│ │ ├── Table.js
│ │ └── index.js
│ └── features
│ ├── index.js
│ ├── detailView
│ │ └── components
│ │ └── Detail.js
│ └── listView
│ └── components
│ └── List.js
└── index.js
复制代码
有的组件跨 Feature 复用,有的组件同 Feature 内跨 Page 复用。 为了保证良好的可维护性,共享组件的组织应该遵循明确的规则。
下面是一个供参考的方案。(components 目录下的 index.js 只负责 export 组件,不实现具体功能)
src
├── components
│ ├── Notification.js
│ └── index.js
├── features
│ ├── posts
│ │ └── components
│ │ └── index.js
│ └── users
│ ├── components
│ │ ├── Table.js
│ │ └── index.js
│ └── features
│ ├── index.js
│ ├── detailView
│ │ └── components
│ │ └── Detail.js
│ └── listView
│ └── components
│ ├── List.js
│ ├── Title.js
│ └── components
│ ├── index.js
│ ├── Pagination.js
│ └── components
│ └── index.js
└── index.js
复制代码
请思考一下,在上述项目结构中, src/features/users/pages/listView/components/List.js
能够共享使用的组件有哪些?
接下来,咱们一一来看:
src/components
内放置的是应用范围内的共享组件,因此,List 能够使用其中的全部组件。src/features/posts/components
跟 List 属于不一样 Feature,因此,没法使用其中组件。src/features/users/pages/detailView/components
,虽然跟 List 属于同一个 Feature,若是容许从listView
到 detailView
的『同层引用』,也会增长 Feature 内的文件引用复杂度。因此,这类引用也是要被禁止的。src/features/users/pages/listView/components/components
(与 List 同级的components 目录),List 的子组件就放在同级的 components 目录中,因此,容许访问。src/features/users/pages/listView/components/components/component
(与 List 同级的 components 目录的子级 components),『跨层引用』也会增长复杂度,因此,也不容许此类引用。把上述状况,归结成一句话就是:
除了同目录文件,组件只能引用其所在文件各级路径下的 components 目录。
将 src/features/user/pages/listView/components/List.js
按照上述规则展开:
src/features/user/pages/listView/components/components
src/features/user/pages/listView/components
src/features/user/pages/components
src/features/user/components
src/features/components
src/components
看到这,可能找个了一个熟悉的身影。 是的,它跟 CommonJS 中的模块查找规则很相似。
components 目录里放什么?components 目录的严格意义是,放置仅供同级组件复用的子组件。例如:上述与 List 同级的 components 目录,应该存放仅供 List、Title 复用的子组件。
在人才管理、财务管理等企业级应用中,仅有 App 和 Feature 已经不能如期地对应上产品结构了。
这时,咱们须要一个新的逻辑层次,如:Solution。 一个 Solution 包含若干个 App,每一个 App 有多个 Feature。
有了 Solution 新的逻辑层次,根据内聚性,能够把原来的 Feature 分拆到不一样的 App 中。 相比于 Feature,App 间的耦合性更小,甚至能够看成独立的部署单元。
项目的规模大了,依赖管理上的挑战也出现了。 Feature 之间要减小依赖, App 之间更要进一步隔离, 跨 App 复用的模块,就不能简单地 import 了。
为了减小 App 之间的耦合,须要将复用单元须要封装成 package, 而后,各个 App 在 package.json 中声明依赖。 一样,在 Solution 的眼中,各个 App 也是 package。
经过从 multi-module 到 multi-package 的转变,耦合减少了, 但给开发也带了新的成本,这些问题可借助于 Lerna 等工具解决。
使用 Lerna 能够解决依赖和版本管理的问题,除此以外,还要作好 package 的分层设计。
App 是一个 package,App 依赖的模块也是一个 package, 可是,两类 package 是不一样『质』的。
为了让项目结构更清晰、易理解,咱们须要区分这些 package,进行分层设计。例如:
packages
├── solutions
│ ├── login.sln
│ ├── finance.sln
│ └── people.sln
├── apps
│ ├── portal.app
│ ├── financial-management.app
│ ├── recruting.app
│ └── global-search.app
├── biz-libs
│ ├── workflow-engine
│ ├── rendering-engine
│ └── network
├── base-libs
│ ├── ui-components
│ └── animations
└── vendor-libs
├── router
└── state-manager
复制代码
与 File-Type First 的文件组织方式相比, Feature First 在提高大规模项目的可维护性上有着明显的优点。
在组织应用内的共享组件时,能够遵循跟 CommonJS 相似的模块查找方式:组件只引用其所在文件各级路径下的 components 目录
。
在企业级应用等大规模项目中,能够经过引入新的逻辑层次和多包管理,进一步控制项目复杂度。
文章做者:孔常柱
BDEEFE 在全国各地长期招聘优秀的前端工程师,招聘需求了解下?