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阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容