軟件系統變得複雜的三個成因是規模、結構與變化。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
(圖片來源: A feature based approach to React development)『功能特性』分離redux
(圖片來源: Why React developers should modularize their applications?)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 在全國各地長期招聘優秀的前端工程師,招聘需求瞭解下?