Rekit 2.0 構建基于React+Redux+React-router的可擴展Web應用

Rekit 2.0 出了好用的新特性!

文章翻譯來自: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. 計數器: 花費1分鐘創建一個簡單計數器!

  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:

Rekit Demo

頁面由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 portal

它不僅提供了用于創建/重命名/移動/刪除Rekit APP元素的Web UI,而且還提供了用于分析/構建/測試Rekit應用的許多測試工具。

從網頁面板中,您發現尚未生成的測試覆蓋報告,不要猶豫,點擊運行測試按鈕,你會快速的發現Rekit portal是怎樣用超級簡單的方式做這些工作的。

有關Rekit portal的更多介紹,在本文的后面章節會做出詳細闡述。

何處著手

Rekit會默認創建一個SPA,您可以根據需要編輯根容器來定義自己的容器布局,源文件位于src/features/home/App.js

兩個簡短視頻教程

在welcome page有兩個實例,它們也是Redux官方網站的演示。現在讓我們看看如何用Rekit創建它們。

  1. 一分鐘用Rekit創建一個計數器

  1. 創建一個Reddit列表(5分鐘)。

就這樣

你已經創建了你的第一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 thunkasync actions。當創建一個async-action時,Rekit創建代碼樣板來處理請求的開始,請求等待,請求成功,請求失敗action類型。在reducer中維護requestpendingrequestError狀態。使用下面的命令行工具,它會自動創建一個async操作的樣板文件,只需要在不同的技術構件中填充應用程序邏輯:即可。

rekit add action home/doRequest [-a]

-a標志表明它是否是一個異步的action,否則同步action。

或者,你可以安裝插件rekit-plugin-redux-sagaredux-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.jsMyComponent.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 type HOME_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。

項目資源管理器

項目資源管理器通過將源文件按featuresactionscomponents分組來提供更有意義的項目文件夾結構視圖。您可以很容易地看到功能結構,而不僅僅是文件夾結構,您可以在Rekit portal的左側看到它:

除了顯示項目結構之外,它還提供了一些上下文菜單來管理諸如components之類的項目元素。

顯示面板

顯示面板提供了項目的總體狀態視圖,如概覽圖,測試覆蓋率等。

概覽圖

顯示面板中最引人注目的部分是概覽圖。這是一個關于Rekit項目架構的直觀視圖。它具有交互性,您可以把鼠標移動到features、 components或、actions上,來查看某些特定元素的關系。您還可以單擊一個節點深入其中,下面的信息被概覽圖所涵蓋:

  1. 模塊之間的關系。

  2. features的相對大小。

  3. 一個feature是如何組成的。

當鼠標經過一個元素時,圖表將突出顯示當前元素和與當前元素有關的關系。

理想情況下,不應該在features之間存在循環依賴。所以它們是可插入的并且更容易理解。但是在實際項目中,您需要平衡架構和開發效率。因此,如果在features之間有輕量級的循環依賴關系,而原則是避免太多此類依賴關系,這是可以接受的。當某些類型的依賴關系變得過于復雜時,您可以延遲刪除依賴項進行重構。

這里列出了不同顏色和線條的含義:

元素圖

雖然概覽圖展示了項目的總體架構,但元素圖提供了所選元素和其他元素之間的關系,它有助于快速理解一個模塊,并幫助找出過于復雜的模塊。

當您從項目資源管理器或概覽圖中單擊一個元素時,它將默認顯示元素圖:

element-diagram

測試覆蓋率

Rekit使用istanbul生成測試覆蓋率報告。在對項目運行所有測試之后,測試覆蓋率將有效。運行單個測試或文件夾的測試將不會生成覆蓋率報告。注意,如果一些測試失敗,報告數據可能是不完整的。

您可以看到來自顯示面板的總體測試覆蓋率報告,或者來自詳細頁面的由istanbul-nyc生成的原始測試覆蓋率報告。

管理項目的元素

Rekit portal將命令行工具封裝到UI對話框中,可以直觀地創建、重命名或刪除components、actions等。打開對話框,右鍵單擊項目中的某個元素,然后單擊相應的菜單項。

cmd-dialogs

運行構建

Rekit portal執行構建腳本tools/build.js。當單擊菜單項Build時,它將讀取Webpack構建進度數據來更新進度條。盡管build.js是由Rekit創建的,看起來有點復雜,但是在完全理解它的工作原理之后,您可以根據需求更新它。

build

運行測試

Rekit portal執行測試腳本tools/run_test.js。當單擊菜單項Run tests時,腳本接受測試運行的參數,參數可以是單個文件或文件夾。當沒有參數提供時,它運行tests文件夾下的所有測試,并生成測試覆蓋率報告。在下面章節的命令行工具頁的介紹中可以查看到更多詳細信息。

因此,當單擊項目元素組件上的Run test菜單項時,它只執行tools/run_test.js,將當前的組件測試文件作為參數傳遞給腳本。您還可以根據需要來更新你的run_test.js腳本。

test

代碼查看器

它有助于快速查看項目的源代碼。例如,當選擇一個組件時,默認情況下它會顯示圖表視圖,但是您可以切換到代碼視圖,在那里您可以查看組件的源代碼。您還可以輕松地查看樣式代碼或測試文件。目前,Rekit并沒有直接支持編輯代碼,因為它不打算替換您喜愛的文本編輯器。

element-page

命令行工具

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, componentaction 并且定義了怎么 add/rm/mv 它們。

現在,您可以創建一個Rekit插件來改變默認行為,例如Rekit創建異步action或讓Rekit支持基于reselect的新元素類型selector

實際上,有這樣的兩個插件:

  1. rekit-plugin-redux-saga: 允許Rekit在創建異步操作時使用redux-saga而不是redux-thunk
  2. 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) {},
};

您可以只導出一些addremovemove。例如,如果只定義add命令,那么當執行rekit mv action ...時,它將默認回歸到Rekitmvactions。

您還可以分開創建3個插件addremovemove,它們接受相同的元素類型。

2. defineArgs(addCmd, mvCmd, rmCmd)

Rekit使用argparse來解析命令參數。這種方法允許為命令行工具自定義參數。這里的addCmdmvCmdrmCmd都是全局Rekit命令的子命令。根據argparse的文檔,您可以為子命令添加更多的選項以滿足您的需求。例如:redux-saga插件定義了一個新選項——thunk,默認情況下redux-saga允許在異步操作中使用redux-thunk,而redux-saga在默認情況下使用。然后您就可以使用:

rekit add action home/my-action -a --thunk

hooks.js

當您想要掛起某些操作時,此文件僅是必需的。即在創建或刪除feature之后,做一些事情。每個元素類型有兩種鉤子點:beforeafter,它們與操作類型相結合,形成多個鉤子點。例如,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自己來管理依賴項。這種方法有幾個優點:

  1. Css可以構建并生成separtely。
  2. React component導入less不是標準的方法,僅是webpack加載器的特性。
  3. 如果組件使用不止一次,webpack的Css-loader會為構建生成重復的Css代碼。

Rekit的使用非常直觀的,如下圖所示:

Styling

一般來說,一個Rekit應用程序的樣式遵循以下幾個規則:

  1. 全局樣式定義在src/styles/global.less,例如css for body,h1,h2…

  2. 每個組件都有一個具有相同名稱的樣式文件,例如,組件SimpleNav。js有一個名為SimpleNave.less的樣式文件。

  3. 每個feature都有一個名為style.less文件。為頁面和組件導入所必需的樣式文件。文件中還定義了所有 feature 的共同樣式。

  4. src/styles/index.less是導入所有feature的style.lessglobal.less樣式的入口文件。

對于其他場景,您可以隨意使用您喜歡的方式。

測試

測試一個React + Redux應用是困難的,你需要知道很多關于 React測試的 libs/tools,了解它們的用法。 但Rekit會為你設置了一切,您只需要在自動生成的測試文件中編寫測試代碼,在 Rekit portal中運行測試,就可以瀏覽器中讀取測試覆蓋率報告。

下面是React測試工作的過程,您可以了解Rekit是如何設置測試過程的。

  1. Istanbul測試覆蓋報告的源代碼。Istanbul本身不支持JSX,但有一個babel插件babel-plugin-istanbul能夠支持運行。

  2. Webpack用mocha-webpack構建所有測試文件,自動找到測試文件進行運行測試。

  3. Mocha運行測試文件的構建版本。

  4. nyc生成測試覆蓋率報告。

所有應用程序測試文件都保存在test/app目錄中。文件夾結構與src文件夾中的源代碼文件夾結構相同。

Rekit如何生成測試文件

在創建新頁面、組件或action時,Rekit 自動為它們創建測試用例。即使您不向生成的測試用例添加任何代碼,它們也可以幫助您避免簡單的錯誤,如:

  1. 確保了一個組件是否能渲染成功是通過檢查DOM根節點的存在和正確的css類來完成的。

  2. 通過檢查前后狀態是否一致來確保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編寫的,所以測試更簡單:

  1. Istanbul 的源代碼。
  2. Mocha運行測試用例。
  3. 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運行的,那么所有測試覆蓋率報告都將涉及。

可以看到如下的不同覆蓋率報告:

  1. all: coverage/lcov-report/index.html
  2. app: coverage/app/lcov-report/index.html
  3. cli: coverage/cli/lcov-report/index.html

打包

Webpack總被認為是構建React應用程序的最困難的地方。幸運的是,Rekit為你配置了一切,下面有兩個webpack配置文件對應不同的用法:

  • webpack.config.js:是webpack用于開發的配置工廠,dlldist打包。
  • 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

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內容