美文网首页开发工具字节前端饥人谷技术博客
Rekit 2.0 构建基于React+Redux+React-

Rekit 2.0 构建基于React+Redux+React-

作者: moofyu | 来源:发表于2017-07-15 16:57 被阅读753次

    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分钟创建一个简单计数器!

    Rekit Demo
    1. Reddit API的用法: 在Reddit上使用异步actions来显示最新的react.js主题.

    Rekit Demo

    开始

    尝试使用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创建一个计数器

    couter
    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的左侧看到它:

    project-explorer

    除了显示项目结构之外,它还提供了一些上下文菜单来管理诸如components之类的项目元素。

    显示面板

    显示面板提供了项目的总体状态视图,如概览图,测试覆盖率等。

    dashboard

    概览图

    显示面板中最引人注目的部分是概览图。这是一个关于Rekit项目架构的直观视图。它具有交互性,您可以把鼠标移动到features、 components或、actions上,来查看某些特定元素的关系。您还可以单击一个节点深入其中,下面的信息被概览图所涵盖:

    1. 模块之间的关系。

    2. features的相对大小。

    3. 一个feature是如何组成的。

    当鼠标经过一个元素时,图表将突出显示当前元素和与当前元素有关的关系。

    理想情况下,不应该在features之间存在循环依赖。所以它们是可插入的并且更容易理解。但是在实际项目中,您需要平衡架构和开发效率。因此,如果在features之间有轻量级的循环依赖关系,而原则是避免太多此类依赖关系,这是可以接受的。当某些类型的依赖关系变得过于复杂时,您可以延迟删除依赖项进行重构。

    这里列出了不同颜色和线条的含义:

    dashboard

    元素图

    虽然概览图展示了项目的总体架构,但元素图提供了所选元素和其他元素之间的关系,它有助于快速理解一个模块,并帮助找出过于复杂的模块。

    当您从项目资源管理器或概览图中单击一个元素时,它将默认显示元素图:

    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

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

      本文标题:Rekit 2.0 构建基于React+Redux+React-

      本文链接:https://www.haomeiwen.com/subject/dpyahxtx.html