文章翻譯來自:http://rekit.js.org/
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
前言
前端開發前景一片美好,大量的框架和工具來幫助你開發復雜的項目。但也面臨著許多痛點,你需要持續學習不斷更新的框架和工具,如何利用各種框架來提高前端的開發質量和效率是大家關注的重點。
為了使項目遵循一種最佳,最優的實踐方式,本文介紹一個名為Rekit的工具。
在我的理解中,一個優秀的Web項目應該考慮一下幾點:
1. 易開發:開發功能需求時,無需關注復雜的技術架構
2. 易擴展:在擴展新功能時,不需要對架構進行改動,新功能不對舊功能產生影響
3. 易維護:架構和代碼結構清晰,可讀性強,新開發人員上手快
4. 易測試:代碼模塊化強,易于單元測試
這幾個點是相互依賴相互制約的,我們可以根據項目的實際需求來權衡各個點,使項目能夠達到最優的狀態。
本文將介紹Rekit是如何基于React+Redux+React-router創建可擴展Web應用的,它為創建React app提供了全功能解決方案。
Rekit創建應用程序遵循一般的最佳實踐,創建的應用程序具有可擴展性、可測試性和可維護性,優化了應用程序邏輯的歸類和解耦。您只需專注于業務邏輯,而不需花費大量的時間來處理庫、模式、配置。
除此之外,Rekit還提供管理項目的強大工具:
1. 命令行工具: 您可以使用這些工具來創建/重命名/移動/刪除項目元素,比如components、 actions等。
2. Rekit portal:它是一個新的開發工具,附帶了Rekit 2.0。它不僅提供了用于創建/重命名/移動/刪除Rekit應用的web UI,而且還提供了用于分析/構建/測試Rekit應用的測試工具,你可以將Rekit portal視為React開發的IDE。參見在線演示: https://rekit-portal.herokuapp.com
下面是兩個快速視頻演示(需要翻墻):
- 計數器: 花費1分鐘創建一個簡單計數器!
-
Reddit API的用法: 在Reddit上使用異步actions來顯示最新的
react.js
主題.
開始
嘗試使用Rekit最簡單的方式是創建一個Rekit APP并且玩轉它,僅僅3步:
1. 安裝Rekit
npm install -g rekit
2. 創建app
$ rekit create my-app
$ cd my-app
$ npm install
3. 運行
$ npm start
進入welcome page
應用程序應該在幾秒鐘內啟動,你可以輸入下面URL訪問:http://localhost:6075
如果一切正常,您應該能夠看到如下的welcome page:
頁面由3部分組成:
1. 一個簡單的導航組件。它讀取整個應用程序的路由配置,并生成指向不同頁面的鏈接。
2. 使用同步actions的計數器演示。通過示例,您可以快速地看到component、actions和reducers三者是怎樣協同工作的。
3. 通過Reddit來演示獲取reactjs
最新主題。這僅僅是來自官方Redux網站的例子:https://redux.js.org/docs/advanced/ExampleRedditAPI.html.
它體現了Redux應用的異步actions。但Rekit版本使用每個action一個獨立文件
的模式,增加了錯誤處理,這是程序實踐開發的共同要求。
試用Rekit portal
Rekit portal是裝載在Rekit 2.0上的全新開發工具。當一個Rekit APP啟動時,Rekit portal也會自動啟動,本地默認啟動地址為:http://localhost:6076
它不僅提供了用于創建/重命名/移動/刪除Rekit APP元素的Web UI,而且還提供了用于分析/構建/測試Rekit應用的許多測試工具。
從網頁面板中,您發現尚未生成的測試覆蓋報告,不要猶豫,點擊運行測試按鈕,你會快速的發現Rekit portal是怎樣用超級簡單的方式做這些工作的。
有關Rekit portal的更多介紹,在本文的后面章節會做出詳細闡述。
何處著手
Rekit會默認創建一個SPA,您可以根據需要編輯根容器來定義自己的容器布局,源文件位于src/features/home/App.js
。
兩個簡短視頻教程
在welcome page有兩個實例,它們也是Redux官方網站的演示。現在讓我們看看如何用Rekit創建它們。
就這樣
你已經創建了你的第一Rekit APP,并且嘗試了強大的Rekit工具。現在你可以在下面章節閱讀到更多有關Rekit的細節。
Rekit app 架構
通過Rekit,僅使用一行命令就可以創建一個React app, 而不需要其它的額外配置。該應用程序的設計具有可擴展性、可測試性和可維護性,以滿足實際應用程序的要求。
Rekit一個關鍵理念是將一個大的應用程序劃分為功能塊,我們把它稱為features,每個feature是應用程序的某個功能特性,它體積小、解耦性好,易于理解和維護。
一個React App 已經自然的由組件樹構建UI。實際上通過結合Redux reducers,定義React router配置的子路由,我們也可以把整個應用程序的store,路由配置成小塊。通過這樣做,我們可以將可以把復雜應用程序的管理變成對應用塊的管理,請參閱下圖,以了解整個Rekit應用程序架構。
每個feature是一個小的app
,這很容易理解,一個大的應用程序由許多這樣的feature組成。
目錄結構
無論是 Flux 還是 Redux,官方提供的示例都是以技術邏輯來組織文件目錄,雖然這種方式在技術上清晰,但在實際項目中存在許多缺點:
1.難以擴展。當應用功能增加,規模變大時,一個 components 文件夾下可能會有幾十上百個文件,組件間的關系極不直觀。
2.難以開發。在開發某個功能時,通常需要同時開發組件,action,reducer 和樣式。把它們分布在不同文件夾下嚴重影響開發效率。尤其是項目復雜之后,不同文件的切換會消耗大量時間。
如上圖所示,Rekit使用一個特殊的文件夾結構創建一個應用程序。根據features將應用程序邏輯分組,每個feature都包含自己的components、actions、路由配置等。
|-- project-name
| |-- src
| | +-- common
| | |-- features
| | | |-- home
| | | | +-- redux
| | | | |-- index.js
| | | | |-- DefaultPage.js
| | | | |-- DefaultPage.less
| | | | |-- route.js
| | | | |-- styles.less
| | | | |-- ...
| | | +-- feature-1
| | | +-- feature-2
| | +-- styles
| --- tools
| | +-- plugins
| | |-- server.js
| | |-- build.js
| | |-- ...
|-- .eslintrc
|-- .gitignore
|-- webpack-config.js
|-- ...
概念
Rekit不封裝或更改任何React、Redux和React router的API,只根據最佳實踐來創建應用程序,并提供管理項目的工具。因此,項目中沒有新的概念(也許除了feature之外),如果你能理解React, Redux 和 React router,你能夠很容易的理解Rekit項目。
下面介紹下這些概念是如何被Rekit管理和使用的。
Feature
Feature是項目的頂層概念,它是Rekit的核心理念,一個feature實際上是描述應用程序某些功能的一種自然方式。例如,一個電子商店應用程序通常包含以下功能:
- customer 管理基本客戶信息。
- product: 銷售管理產品。
- category: 管理產品類別。
- order:管理銷售訂單。
- user: 系統管理員管理。
- etc...
一個feature 通常包含多個actions、組件或路由規則,一個Rekit應用總是由多個feature組成。通過種方法,一個大的應用程序可以被劃分為多個小的、完全解耦的、易于理解的features。
當創建Rekit應用時,會自動生成兩個默認的features。
1. common:它是放置所有跨feature元素(如components,actions,等)的地方。在Rekit1.0版本中,有一個單獨的components目錄來用來存放common components。React2.0版本中。我們把它們放在一個common feature目錄中,通過這個優化,減少了conepts的數量,使目錄結構更加簡單一致。
2. home:項目的基本feature和應用程序的起點,通常把最基本的功能放在這里,如整體布局容器,基本應用程序邏輯等。
然而這僅僅是Rekit推薦的方式,如果您愿意,你可以重命名目錄或刪除默認功能。
為了快速領悟feature概念,你可以看看Rekit portal的在線演示:
https://rekit-portal.herokuapp.com
查看更多有關feature的介紹: feature oriented architecture
Component
根據Redux理論,組件(components)可以分為兩類:容器型組件(container
)和展示型組件(presentational
)。Rekit可以很容易的創建它們。
rekit add component home/Comp [-c]
-c
標志表明它是一個容器型組件,否則是一個展示型組件。Rekit將使用不同的模板生成相應的組件。
為了能夠使用React router,組件是一些URL模式的代表,Rekit允許指定-u
參數:
rekit add component home/Page1 -u page1
這將在feature目錄的router.js
文件中定義一個路由規則,然后你可以在本地輸入: http://localhost:6075/home/page1 來訪問組件。
Action
說的是Redux action。有兩種類型的actions:同步(sync
)和異步(async
)。正如Redux的文檔所描述的,異步action實際上不是一個新概念,而是提出了異步操作的數據和工作流程。
默認情況下,Rekit使用Redux thunk
的async actions
。當創建一個async-action
時,Rekit創建代碼樣板來處理請求的開始,請求等待,請求成功,請求失敗action類型。在reducer中維護requestpending
,requestError
狀態。使用下面的命令行工具,它會自動創建一個async操作的樣板文件,只需要在不同的技術構件中填充應用程序邏輯:即可。
rekit add action home/doRequest [-a]
-a
標志表明它是否是一個異步的action,否則同步action。
或者,你可以安裝插件rekit-plugin-redux-saga用redux-saga創建異步actions。
Reducer
這里講的是Redux reducer。Rekit會按照官方方式為reducers重新組織代碼結構。可以閱讀下面章節的one action one file 來獲取更多的介紹。
每個action一個獨立文件
這可能是Rekit方法中最具自主性的部分,也就是:one action one file
,把相應的reducer放在同一個文件中。
這個想法來自Redux開發所帶來的痛點:它幾乎總是在創建一個新的aciton后,寫一個reducer。
就拿計數器組件的例子來說,在創建新的action COUNTER_PLUS_ONE
后,我們立即需要在reducer中來處理它,官方的做法是將代碼分開,分別寫在actions.js和reducers.js兩個文件中。現在,我們創建一個名為counterPlusOne.js
的新文件,把下面的代碼放入里面。
import {
COUNTER_PLUS_ONE,
} from './constants';
export function counterPlusOne() {
return {
type: COUNTER_PLUS_ONE,
};
}
export function reducer(state, action) {
switch (action.type) {
case COUNTER_PLUS_ONE:
return {
...state,
count: state.count + 1,
};
default:
return state;
}
}
根據我的經驗,大多數reducers 都有相應的actions,它很少在全局中使用。因此,將其放在一個文件中是合理的,且使開發更容易。
這里的reducer不是一個標準的Redux reducer,因為它沒有一個初始狀態。它只用在feature的根reducer,它通常被稱為reducer
。這樣,根reducer就可以從action 模塊自動加載它。
對于異步actions,action文件可以包含多個action,因為它需要處理錯誤。對于Rekit應用,每個feature都包含一個命名為redux
的文件夾,在這個文件夾中放置actions, constants 和 reducers。
如何跨功能actions?
雖然不是很常見,但是有些action是可能被多個reducer處理的。例如,對于站內聊天功能,當收到一條新消息時:
如果聊天框開著,那么直接顯示新消息。
否則,顯示一條通知提示有新的消息。
可見,NEW_MESSAGE
這個action需要被不同的reducer處理。從而能夠在不同的UI組件做不同的展現。為了處理這類 action,每個功能文件夾下都有一個 reducer.js 文件,在里面可以處理跨功能的action。
雖然不同 action 的 reducer 分布在不同的文件中,但它們和功能相關的 root reducer 共同操作同一個狀態,即同一個 store 分支。因此 feature/reducer.js 具有如下的代碼結構:
import initialState from './initialState';
import { reducer as counterPlusOne } from './counterPlusOne';
import { reducer as counterMinusOne } from './counterMinusOne';
import { reducer as resetCounter } from './resetCounter';
const reducers = [
counterPlusOne,
counterMinusOne,
resetCounter,
];
export default function reducer(state = initialState, action) {
let newState;
switch (action.type) {
// Put global reducers here
default:
newState = state;
break;
}
return reducers.reduce((s, r) => r(s, action), newState);
}
它負責引入不同 action 的 reducer,當有 action 過來時,遍歷所有的 reducer 并結合需要的全局 reducer 來實現對 store 的更新。所有功能相關的 root reducer 最終被組合到全局的 Redux root reducer 從而保證全局只有一個 store 的存在。
需要注意的是,每當創建一個新的 action 時,都需要在這個文件中注冊。因為其模式非常固定,我們完全可以使用工具來自動注冊相應的代碼。Rekit 可以幫助做到這一點:當創建 action 時,它會自動在 reducer.js 中加入相應的代碼,既減少了工作量,又可以避免出錯。
使用這種方式,可以帶來很多好處,比如:
1.易于開發:當創建 action 時,無需在多個文件中跳轉;
2.易于維護:因為每個 action 在單獨的文件,因此每個文件都很短小,通過文件名就可以定位到相應的功能邏輯;
3.易于測試:每個 action 都可以使用一個獨立的測試文件進行覆蓋,測試文件中也是同時包含對 action 和 reducer 的測試;
4.易于工具化:因為使用 Redux 的應用具有較為復雜的技術結構,我們可以使用工具來自動化一些邏輯。現在我們無需進行語法分析就可以自動生成代碼。
5.易于靜態分析:全局的 action 和 reducer 通常意味著模塊間的依賴。這時我們只要分析功能文件夾下的 reducer.js,即可以找到所有這些依賴。
命名規范
Rekit通過自動轉換輸入來強制達到一致的命名規則。無論是命令行工具或Rekit portal都要遵循以下命名規則來創建features、 components 和 actions,如果手動創建它們,也應該遵循這些規則。
-
feature
:文件夾名稱:kebab case( 短橫線隔開)
。例如:rekit add feature myFeature
將創建一個文件夾,命名為my-feature
。 -
redux store
:駝峰拼寫法。當添加一個feature時,Rekit將把 feature reduce合并到根reducer,并以駝峰式命名作為分支名稱。 -
url path
: 短橫線隔開。它將把-u MyPage
參數修改映射到一個頁面。對于這個命令,它將URL路徑定義為路由配置中的my-page
。 -
component
:文件名和樣式文件名:駝峰式大小寫。例如:rekit add component feature/my-component
將創建文件MyComponent.js
和MyComponent.less
。 -
action
: 函數名: 駝峰拼寫法. 例如:rekit add action feature/my-action
將會在actions.js中創建一個名為myAction
的函數 。 -
action type
: 常量名稱和值:upper snake case
。Action types是在創建action時創建的。例如:rekit add action home/my-action
將會創建一個 action typeHOME_MY_ACTION
。
如您所見,任何作為參數的名稱都將被轉換。因此,Rekit應用程序的所有變量都是一致的,易于理解。
Rekit core
Rekit core提供了用于管理Rekit應用的核心功能,常被用在Rekit命令行工具和Rekit portal中。
當一個Rekit應用程序被創建時,它會自動添加rekit-core
作為一個依賴項。當您執行rekit add feature f1
這樣的命令時,它會找到當前安裝的rekit-core
,在項目中添加了一個feature
。因此,rekit-core
不是全局性的,而是獨立于項目的。不同的Rekit應用程序可以使用不同版本的rekit-core
。保證了其它項目升級rekit-core
時不會破壞現有的Rekit應用程序。
API 參考
Rekit core APIs 有著良好地模塊化和文檔化,是創建定制Rekit插件的基礎。
您可以查看API文檔:http://rekit.js.org/api。
您可以根據rekit-core
創建自己的插件。
Rekit portal
Rekit portal是一個新的開發工具,附帶了Rekit 2.0,它在管理和分析您的Rekit項目中占有核心地位。Rekit portal本身也是由Rekit創建的,因此,它也是學習Rekit所參考的一個很好的例子。
為了快速查看Rekit portal是如何工作的,您可以查看在線演示。
主要功能點
提供一種更直觀的方式來創建、重命名、移動或刪除features、components或actions,而不是CLI,就如同使用eclipse這樣的IDE創建Java類一樣。
通過源代碼生成項目體系結構的圖表報告。因此,新團隊成員或者你自己,很短時間內就能夠上手項目。
只需右鍵單擊,就可以輕松運行測試單個組件或action。
不用打開終端就可運行構建。
集成測試覆蓋報告。
安裝
不需要手動安裝Rekit portal,當創建一個新的Rekit應用程序時,rekit-portal
將自動依賴于npm模塊。打開http://localhost:6076即可訪問Rekit portal。
項目資源管理器
項目資源管理器通過將源文件按features
、actions
、components
分組來提供更有意義的項目文件夾結構視圖。您可以很容易地看到功能結構,而不僅僅是文件夾結構,您可以在Rekit portal的左側看到它:
除了顯示項目結構之外,它還提供了一些上下文菜單來管理諸如components
之類的項目元素。
顯示面板
顯示面板提供了項目的總體狀態視圖,如概覽圖,測試覆蓋率等。
概覽圖
顯示面板中最引人注目的部分是概覽圖。這是一個關于Rekit項目架構的直觀視圖。它具有交互性,您可以把鼠標移動到features、 components或、actions上,來查看某些特定元素的關系。您還可以單擊一個節點深入其中,下面的信息被概覽圖所涵蓋:
模塊之間的關系。
features的相對大小。
一個feature是如何組成的。
當鼠標經過一個元素時,圖表將突出顯示當前元素和與當前元素有關的關系。
理想情況下,不應該在features之間存在循環依賴。所以它們是可插入的并且更容易理解。但是在實際項目中,您需要平衡架構和開發效率。因此,如果在features之間有輕量級的循環依賴關系,而原則是避免太多此類依賴關系,這是可以接受的。當某些類型的依賴關系變得過于復雜時,您可以延遲刪除依賴項進行重構。
這里列出了不同顏色和線條的含義:
元素圖
雖然概覽圖展示了項目的總體架構,但元素圖提供了所選元素和其他元素之間的關系,它有助于快速理解一個模塊,并幫助找出過于復雜的模塊。
當您從項目資源管理器或概覽圖中單擊一個元素時,它將默認顯示元素圖:
測試覆蓋率
Rekit使用istanbul生成測試覆蓋率報告。在對項目運行所有測試之后,測試覆蓋率將有效。運行單個測試或文件夾的測試將不會生成覆蓋率報告。注意,如果一些測試失敗,報告數據可能是不完整的。
您可以看到來自顯示面板的總體測試覆蓋率報告,或者來自詳細頁面的由istanbul-nyc生成的原始測試覆蓋率報告。
管理項目的元素
Rekit portal將命令行工具封裝到UI對話框中,可以直觀地創建、重命名或刪除components、actions等。打開對話框,右鍵單擊項目中的某個元素,然后單擊相應的菜單項。
運行構建
Rekit portal執行構建腳本tools/build.js
。當單擊菜單項Build
時,它將讀取Webpack構建進度數據來更新進度條。盡管build.js
是由Rekit創建的,看起來有點復雜,但是在完全理解它的工作原理之后,您可以根據需求更新它。
運行測試
Rekit portal執行測試腳本tools/run_test.js
。當單擊菜單項Run tests
時,腳本接受測試運行的參數,參數可以是單個文件或文件夾。當沒有參數提供時,它運行tests
文件夾下的所有測試,并生成測試覆蓋率報告。在下面章節的命令行工具頁的介紹中可以查看到更多詳細信息。
因此,當單擊項目元素組件上的Run test
菜單項時,它只執行tools/run_test.js
,將當前的組件測試文件作為參數傳遞給腳本。您還可以根據需要來更新你的run_test.js
腳本。
代碼查看器
它有助于快速查看項目的源代碼。例如,當選擇一個組件時,默認情況下它會顯示圖表視圖,但是您可以切換到代碼視圖,在那里您可以查看組件的源代碼。您還可以輕松地查看樣式代碼或測試文件。目前,Rekit并沒有直接支持編輯代碼,因為它不打算替換您喜愛的文本編輯器。
命令行工具
Rekit提供了一組命令行工具來管理Rekit項目的 components, actions和路由規則。它們作為JavaScript模塊在rekit-core
中實現,Rekit將它們封裝為命令行工具,實際上rekit-core
也由Rekit Portal
使用。
Create an app
您可以使用以下命令創建一個應用程序:
rekit create <app-name> [--sass]
這將會在當前目錄下創建一個名為app-name
的應用程序。--sass
標記表明允許使用sass而不是less`來當作css編譯器。
Create a plugin
您可以使用以下命令創建一個插件:
rekit create-plugin <plugin-name>
如果當前目錄是在Rekit項目中,那么將創建一個本地插件,否則創建一個公共插件。
想了解更多信息,請閱讀以下文件: http://rekit.js.org/docs/plugin.html
安裝一個插件
如果你想通過npm使用一個插件,請使用下面的命令:
rekit install <plugin-name>
這將執行插件的install.js
腳本進行初始化,并添加名為rekit.plugins
的插件到package.json中。
Rekit tools
Rekit工具是創建的應用程序附帶的純腳本,它們被放入在你應用程序的tools
文件夾中,并且支持編輯以滿足項目的額外要求。
tools/server.js
這個腳本用于啟動開發服務器,默認情況下,它啟動3個服務器,包括:webpack dev server、Rekit portal和 build result server,您只能通過參數啟動某個服務器。
用法:
node tools/server.js [-m, --mode] [--readonly]
-
mode
: 如果沒有提供,則啟動所有3個開發服務器。否則,只啟動指定的開發服務器。它可以是:dev: webpack dev server
portal: Rekit portal
build: start a static express server for build folder
readonly
:在只讀模式下啟動Rekit portal。啟動Rekit portal服務器只用于探索項目結構是很有用的。例如,Rekit portal演示是在只讀模式上運行。
npm
腳本也是有效的: npm start
tools/run_test.js
這個腳本有助于運行一個或多個單元測試。它接受參數來判斷哪個測試文件應該運行。
用法:
node tools/run_test.js <file-pattern>
文件模式與mocha
所接受的相同。如果沒有指定file-pattern
,則運行所有測試并生成測試覆蓋率報告。否則運行測試與file-pattern
匹配。
例如:
node tools/run_test.js features/home/redux/counterPlusOne.test.js // run test of a redux action
node tools/run_test.js features/home // run all tests of home feature
node tools/run_test.js // run all tests and generate test coverage report
也可以運行npm腳本: npm test
.
tools/build.js
這個腳本用于構建項目。
用法:
node tools/build.js
也可以使用npm
腳本:npm run build
。它將項目構建到build
文件夾中。
管理 features, components and actions
這是每日Rekit發展的關鍵。您將使用以下命令來管理Rekit元素。
注意:盡管所有命令都放在
rekit
命令下,也就是rekit add component home/comp1
。實際上Rekit會在你的應用程序中找到本地的rekit-core
包來完成運行。因此,如果這些應用程序依賴于不同版本的rekit-core
,那么在不同的rekit應用程序下執行rekit命令可能會有不同的行為。
所有這些命令都有類似的模式:
-
rekit add <type>
: 添加一個類型的元素。 -
rekit mv <type>
: 移動/重命名一個類型的元素。 -
rekit rm <type>
: 刪除一個類型的元素。
所有命令都支持[-h]參數來查看使用幫助。即rekit add -h
。
下面是所有Rekit命令來管理項目的元素列表
Commands | Description |
---|---|
rekit add feature <feature-name> | Add a new feature. |
rekit mv feature <old-name> <new-name> | Rename a feature. |
rekit rm feature <feature-name> | Delete a feature. |
rekit add component <component-name> [-u] [-c] | Add a new component.-u : specify the url pattern of the component. -c : it's a container component connected to Redux store. |
rekit mv component <old-name> <new-name> | Rename a component. |
rekit rm component <component-name> | Delete a component. |
rekit add action <action-name> [-a] | Add a new action. -u : add an async action. |
rekit mv action <old-name> <new-name> | Rename an action. |
rekit rm action <action-name> | Delete an action. |
插件
Rekit 2.0引入了一個新的插件機制來擴展Rekit的功能。如果您嘗試過Rekit命令行工具,您可能熟悉它的模式:
rekit <add|rm|mv> <element-type> <feature>/</element-name>
內部Rekit支持3種元素類型:: feature
, component
和 action
并且定義了怎么 add/rm/mv
它們。
現在,您可以創建一個Rekit插件來改變默認行為,例如Rekit創建異步action或讓Rekit支持基于reselect的新元素類型selector
。
實際上,有這樣的兩個插件:
-
rekit-plugin-redux-saga: 允許Rekit在創建異步操作時使用
redux-saga
而不是redux-thunk
。 - rekit-plugin-reselect:添加一個基于reselect的新元素類型選擇器。因此,您可以通過Rekit為您的項目管理選擇器。
創建一個插件
要創建一個插件,請使用以下命令:
rekit create-plugin <plugin-name>
如果當前目錄在Rekit項目中,它將創建一個本地插件,否則創建一個公共插件。
插件結構
創建一個插件后,您可以查看文件夾結構。在插件文件夾下可能有一些特殊的文件:
config.js
插件唯一的mandotory文件,它定義了處理的元素類型,必要時定義命令參數。例如:
module.exports = {
accept: ['action'],
defineArgs(addCmd, mvCmd, rmCmd) { // eslint-disable-line
addCmd.addArgument(['--thunk'], {
help: 'Use redux-thunk for async actions.',
action: 'storeTrue',
});
},
};
有兩部分:
1. accept
它是一個數組,用來接收插件要處理的類型元素。當一個類型的元素被定義在這里時,應該有一個名為${ elementType }.js
的模塊。在這里定義了添加、刪除、移動方法來管理這些元素。例如,元素類型action
被定義,在插件文件夾里將會有一個action.js
模塊:
module.exports = {
add(feature, name, args) {},
remove(feature, name, args) {},
move(source, target, args) {},
};
您可以只導出一些add
、remove
和move
。例如,如果只定義add
命令,那么當執行rekit mv action ...
時,它將默認回歸到Rekitmv
actions。
您還可以分開創建3個插件add
、remove
和move
,它們接受相同的元素類型。
2. defineArgs(addCmd, mvCmd, rmCmd)
Rekit使用argparse來解析命令參數。這種方法允許為命令行工具自定義參數。這里的addCmd
、mvCmd
和rmCmd
都是全局Rekit命令的子命令。根據argparse的文檔,您可以為子命令添加更多的選項以滿足您的需求。例如:redux-saga
插件定義了一個新選項——thunk
,默認情況下redux-saga
允許在異步操作中使用redux-thunk
,而redux-saga
在默認情況下使用。然后您就可以使用:
rekit add action home/my-action -a --thunk
hooks.js
當您想要掛起某些操作時,此文件僅是必需的。即在創建或刪除feature
之后,做一些事情。每個元素類型有兩種鉤子點:before
和after
,它們與操作類型相結合,形成多個鉤子點。例如,feature
元素類型有一下鉤子點:
- beforeAddFeature()
- afterAddFeature()
- beforeMoveFeature()
- afterMoveFeature()
- beforeRemoveFeature()
- afterRemoveFeature()
參數只從鉤子目標繼承。也就是說,傳遞給addFeature的任何參數也都傳遞到beforeAddFeature()。
注意,不只是內部元素類型有鉤子點,所有被插件支持的元素類型都有這樣的鉤子點。
例如,redux-saga
插件使用鉤子在添加或刪除features
時進行初始化和初始化。
const fs = require('fs');
const _ = require('lodash');
const rekitCore = require('rekit-core');
const utils = rekitCore.utils;
const refactor = rekitCore.refactor;
const vio = rekitCore.vio;
function afterAddFeature(featureName) {
// Summary:
// Called after a feature is added. Add sagas.js and add entry in rootSaga.js
const rootSaga = utils.mapSrcFile('common/rootSaga.js');
refactor.updateFile(rootSaga, ast => [].concat(
refactor.addImportFrom(ast, `../features/${_.kebabCase(featureName)}/redux/sagas`, null, null, `${_.camelCase(featureName)}Sagas`),
refactor.addToArray(ast, 'featureSagas', `${_.camelCase(featureName)}Sagas`)
));
const featureSagas = utils.mapFeatureFile(featureName, 'redux/sagas.js');
// create sagas.js entry file for the feature
if (!fs.existsSync(featureSagas)) vio.save(featureSagas, '');
}
function afterRemoveFeature(featureName) {
// Summary:
// Called after a feature is removed. Remove entry from rootSaga.js
const rootSaga = utils.mapSrcFile('common/rootSaga.js');
refactor.updateFile(rootSaga, ast => [].concat(
refactor.removeImportBySource(ast, `../features/${_.kebabCase(featureName)}/redux/sagas`),
refactor.removeFromArray(ast, 'featureSagas', `${_.camelCase(featureName)}Sagas`)
));
}
function afterMoveFeature(oldName, newName) {
// Summary:
// Called after a feature is renamed. Rename entry in rootSaga.js
const rootSaga = utils.mapSrcFile('common/rootSaga.js');
refactor.updateFile(rootSaga, ast => [].concat(
refactor.renameModuleSource(ast, `../features/${_.kebabCase(oldName)}/redux/sagas`, `../features/${_.kebabCase(newName)}/redux/sagas`),
refactor.renameImportSpecifier(ast, `${_.camelCase(oldName)}Sagas`, `${_.camelCase(newName)}Sagas`)
));
}
module.exports = {
afterAddFeature,
afterRemoveFeature,
afterMoveFeature,
};
使用插件
對于本地插件,除了創建它之外,您不需要做任何其他事情。如果存在沖突,處理元素類型的優先級最高。
對于公共插件,從npm安裝。您需要在package.json的rekit部分注冊它。
{
...,
"rekit": {
"plugins": ["redux-saga", "apollo"],
},
...,
}
這里你只需要在插件屬性中定義公共插件,以便Rekit能夠加載。本地插件將總是由Rekit加載。注意插件名稱的順序在它們接受相同的元素類型時很重要,而eariers則有更高的優先級。
雖然存在多個插件接受相同的元素類型的沖突,但從高到低的優先級是:本地插件<公共插件< Rekit默認行為。
注意:支持更多元素類型的插件現在只能通過命令行工具使用。
插件開發
對于大多數情況,一個插件只會基于一些模板創建一個bolierplate
代碼;在移動時重構代碼,刪除源文件。Rekit-core
為促進插件開發提供了許多有用的APIs 。您可能只需要編寫這些APIs 來滿足您的需求。
API參考
查看鏈接: http://rekit.js.org/api/index.html
路由
Rekit使用React router作為路由解決方案。它幾乎是React web應用程序的標準方法。通過使用React-router-redux ,可以輕松使用 Redux store來同步路由狀態。
即使對于簡單的應用程序,路由也非常重要,就像傳統的web應用程序需要不同頁面的不同url一樣,SPA也需要將不同的邏輯分組到不同的UI,這是Rekit的頁面理念。
使用Rekit,頁面通常是與URL路徑關聯的元素。每當通過Rekit創建頁面時,它自動定義路由配置中的映射規則。
路由配置
由于Rekit使用面向功能的文件夾結構,因此路由配置也如此,就是是在功能文件夾中定義的所有功能相關的路由配置。每個feature
都有一個定義路由規則的route.js
文件,下面是一個配置示例:
import {
EditPage,
ListPage,
ViewPage,
} from './index';
export default {
path: '',
name: '',
childRoutes: [
{ path: '', component: ListPage, name: 'Topic List', isIndex: true },
{ path: 'topic/add', component: EditPage, name: 'New Topic' },
{ path: 'topic/:topicId', component: ViewPage },
],
};
使用JavaScript定義路由規則
從上面的示例中,我們可以看到route config使用了JavaScript API 。實際上,路由器支持Json for configuration。Rekit使用了它,因此很容易將不同的路由配置放到不同的features
中。下面是一個Json路由配置示例。
const routes = {
path: '/',
component: App,
indexRoute: { component: Dashboard },
childRoutes: [
{ path: 'about', component: About },
{
component: Inbox,
childRoutes: [{
path: 'messages/:id', component: Message
}]
}
]
}
所有和功能相關的路由定義都被全局的根路由配置自動加載,因此,路由加載器具有如下的代碼模式:
import topicRoute from '../features/topic/route';
import commentRoute from '../features/comment/route';
const routes = [{
path: '/rekit-example',
component: App,
childRoutes: [
topicRoute,
commentRoute,
{ path: '*', name: 'Page not found', component: PageNotFound },
],
}];
實際上,這是一個全局路由加載器,類似root reducer, 加載了全部的feature的路由規則。
注意,您不需要維護
src/common/routeConfig.js
。在添加/刪除一個feature時,Rekit將自動添加和刪除路由規則。
使用isIndex屬性代替indexRoute
不像用JSX的方式,用來<IndexRoute ...>
標簽來配置一個路由,JavaScript API的官方indexRoute
配置是Rekit的一個難點,Rekit添加了isIndex屬性支持。
一個路由規則如果設置isIndex: true
,它將成為父索引路由
下面的代碼是自動創建的home feature的路由配置:
import {
DefaultPage,
TestPage1,
TestPage2,
} from './index';
export default {
path: '',
name: 'home',
childRoutes: [
{ path: 'default-page', component: DefaultPage, isIndex: true },
{ path: 'test-page-1', component: TestPage1 },
{ path: 'test-page-2', component: TestPage2 },
],
};
然后,DefaultPage將會成為根路徑的索引路由。
name屬性
您可能已經注意到,route配置規則有一個name
屬性。實際上它只被SimpleNav
組件使用。它只是在dev time中顯示路由配置的一個方便的組件。您已經在歡迎頁面中看到了。對于一個實際的應用程序來說,它可能是無用的。路由配置API的所有其他用法都與官方的方式相同,您可以參考 React-router官方文檔。
樣式
Rekit使用Less作為的css預處理器,因為我已經習慣了它很長時間。Scss支持將成為候選。實際上,在我的選擇中,兩者之間并沒有太大的差別,而且很容易切換。
在許多實踐中,React component中會導入less文件,不同的是,Rekit建議只使用 Less
自己來管理依賴項。這種方法有幾個優點:
- Css可以構建并生成separtely。
- React component導入less不是標準的方法,僅是webpack加載器的特性。
- 如果組件使用不止一次,webpack的
Css-loader
會為構建生成重復的Css代碼。
Rekit的使用非常直觀的,如下圖所示:
一般來說,一個Rekit應用程序的樣式遵循以下幾個規則:
全局樣式定義在
src/styles/global.less
,例如css for body,h1,h2…每個組件都有一個具有相同名稱的樣式文件,例如,組件
SimpleNav。js
有一個名為SimpleNave.less
的樣式文件。每個
feature
都有一個名為style.less
文件。為頁面和組件導入所必需的樣式文件。文件中還定義了所有 feature 的共同樣式。src/styles/index.less
是導入所有feature的style.less
和global.less
樣式的入口文件。
對于其他場景,您可以隨意使用您喜歡的方式。
測試
測試一個React + Redux應用是困難的,你需要知道很多關于 React測試的 libs/tools
,了解它們的用法。 但Rekit會為你設置了一切,您只需要在自動生成的測試文件中編寫測試代碼,在 Rekit portal中運行測試,就可以瀏覽器中讀取測試覆蓋率報告。
下面是React測試工作的過程,您可以了解Rekit是如何設置測試過程的。
Istanbul測試覆蓋報告的源代碼。Istanbul本身不支持JSX,但有一個babel插件babel-plugin-istanbul能夠支持運行。
Webpack用mocha-webpack構建所有測試文件,自動找到測試文件進行運行測試。
Mocha運行測試文件的構建版本。
nyc生成測試覆蓋率報告。
所有應用程序測試文件都保存在test/app
目錄中。文件夾結構與src
文件夾中的源代碼文件夾結構相同。
Rekit如何生成測試文件
在創建新頁面、組件或action時,Rekit 自動為它們創建測試用例。即使您不向生成的測試用例添加任何代碼,它們也可以幫助您避免簡單的錯誤,如:
確保了一個組件是否能渲染成功是通過檢查DOM根節點的存在和正確的css類來完成的。
通過檢查前后狀態是否一致來確保reducer不變。
例如,當創建一個新頁面時,它生成以下測試案例:
it('renders node with correct class name', () => {
const pageProps = {
home: {},
actions: {},
};
const renderedComponent = shallow(
<TestPage1 {...pageProps} />
);
expect(
renderedComponent.find('.home-test-page-1').node
).to.exist;
});
當創建一個action時,它生成下面的reducer測試用例:
it('reducer should handle MY_ACTION', () => {
const prevState = {};
const state = reducer(
prevState,
{ type: MY_ACTION }
);
expect(state).to.not.equal(prevState); // should be immutable
expect(state).to.deep.equal(prevState); // TODO: replace this line with real case.
});
命令行工具測試
所有命令行工具也有保存在test/cli
文件夾中的單元測試用例。不僅在Rekit項目中,而且它們也被復制到創建的應用程序中,這樣您就可以根據測試用例自定義工具。
因為這些工具腳本是用純NodeJs編寫的,所以測試更簡單:
- Istanbul 的源代碼。
-
Mocha
運行測試用例。 - nyc生成測試覆蓋率報告。
run_test.js
您可能已經注意到,測試用例也需要使用webpack構建,然后用Mocha可以運行測試用例。因此,運行單個測試用例文件也需要構建。所以 run_test.js
的創建是為了簡化這個過程。您可以只通過run_test.js
工具運行任何測試用例文件。它也被導出為npm test
。所有的參數都傳遞給run_test.js
腳本。用法如下:
npm test // run all tests under the `test/app` folder
npm test app/features/home // run tests under the `home` feature
npm test app/**/list.test.js // run all tests named list.test.js under app folder
npm test cli // run tests for all command line tools
npm test all // run tests for both app and cli
查看測試覆蓋率報告
如果測試是由app
, cli
or all
運行的,那么所有測試覆蓋率報告都將涉及。
可以看到如下的不同覆蓋率報告:
-
all
: coverage/lcov-report/index.html -
app
: coverage/app/lcov-report/index.html -
cli
: coverage/cli/lcov-report/index.html
打包
Webpack總被認為是構建React應用程序的最困難的地方。幸運的是,Rekit為你配置了一切,下面有兩個webpack配置文件對應不同的用法:
-
webpack.config.js
:是webpack用于開發的配置工廠,dll
和dist
打包。 -
webpack.test.config.js
:測試構建的配置。
使用webpack-dll-plugin提高構建性能
當一個React應用程序變大時,開發構建應用程序會很耗時。webpack-dll-plugin
可以解決這個痛點。有一個很好的文章對其進行了介紹:http://engineering.invisionapp.com/post/optimizing-webpack/
基本思想是將諸如 React, Redux, React-router之類的公共libs構建為一個單獨的dll包。這樣,在每次更改代碼時都不需要構建它們。通過此方法,可以顯著減少構建時間。
Rekit在tools/server.js
集成了打包過程,用npm start
即可運行。腳本檢查dll
構建所需的所有的版本包。因此,當任何依賴版本發生更改時,它將自動構建一個新的dll
。
代碼質量檢查
Rekit使用ESlint對代碼質量進行檢查。并將airbnb javascript guide作為所有代碼的基本規則。.eslintrc
定義了一些細微的變化用于Rekit應用程序的不同部分。
似乎規則表明airbnb經常改變,對于一個新創建的Rekit應用程序,可能有一些eslint錯誤,您需要更新代碼或添加自己的規則以清除這些錯誤。
總結
本文介紹了Rekit工具,通過Rekit工具構建基于React+Redux +React-router的可擴展Web應用,遵循前端開發的最佳實踐。
其核心思想是:
以功能(feature)為單位組件文件夾結構,
每個 action 單獨文件的模式。
使用 React-router 來讓單頁應用(SPA)也擁有傳統 Web 應用的 URL 導航功能
優點:
代碼更加模塊化
降低了功能模塊間的耦合行
應用結構更加清晰直觀。
更多的工具介紹可以訪問其官網:http://rekit.js.org。
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。