Ant Design Pro V5开发指南

Ant Design Pro 是一个基于 umi、dva 和 ant design 的开箱即用的中台前端/设计解决方案。

一、umi和dva

1.1、什么是umi

umi,中文可发音为乌米,是一个可插拔的企业级 react 应用框架。umi 以路由为基础的,支持类 next.js 的约定式路由,以及各种进阶的路由功能,并以此进行功能扩展,比如支持路由级的按需加载。然后配以完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求,目前内外部加起来已有 50+ 的插件。

上面一段来自umi官网介绍,从上面我们得到一个重要信息,umi的一个重要功能就是路由配置。 此外umi整合了webpack的功能,umi相比webpack增加了运行时的能力,同时帮助我们配置了很多 webpack 的预设。也减少了 webpack 升级导致的问题:

1.2、什么是dva

dva 首先是一个基于 redux 和 redux-saga 的数据流方案,可以和umi完美结合。

1.3、 什么是redux-saga

redux-saga是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。

可以想像为,一个 saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。redux-saga是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。

redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。(如果你还不熟悉的话,这里有一些介绍性的链接 通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。(有点像 async/await,但 Generator 还有一些更棒而且我们也需要的功能)。

你可能已经用了 redux-thunk 来处理数据的读取。不同于 redux thunk,你不会再遇到回调地狱了,你可以很容易地测试异步流程并保持你的 action 是干净的。

1.3.1 安装

npm install --save redux-saga

1.3.2 使用示例

假设我们有一个 UI 界面,在单击按钮时从远程服务器获取一些用户数据(为简单起见,我们只列出 action 触发代码)。

class UserComponent extends React.Component {
  ...
  onSomeButtonClicked() {
    const { userId, dispatch } = this.props
    dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
  }
  ...
}

这个组件 dispatch 一个 plain Object 的 action 到 Store。我们将创建一个 Saga 来监听所有的 USER_FETCH_REQUESTED action,并触发一个 API 调用获取用户数据。

sagas.js:

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'

// worker Saga : 将在 USER_FETCH_REQUESTED action 被 dispatch 时调用
function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

/*
  在每个 `USER_FETCH_REQUESTED` action 被 dispatch 时调用 fetchUser
  允许并发(译注:即同时处理多个相同的 action)
*/
function* mySaga() {
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

/*
  也可以使用 takeLatest

  不允许并发,dispatch 一个 `USER_FETCH_REQUESTED` action 时,
  如果在这之前已经有一个 `USER_FETCH_REQUESTED` action 在处理中,
  那么处理中的 action 会被取消,只会执行当前的
*/
function* mySaga() {
  yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}

export default mySaga;

为了能跑起 Saga,我们需要使用 redux-saga 中间件将 Saga 与 Redux Store 建立连接。

main.js:

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'
import mySaga from './sagas'

// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

// then run the saga
sagaMiddleware.run(mySaga)

// render the application

更多使用细节请移步官网

二、脚手架使用

2.1、初始化脚手架

新建一个空的文件夹作为项目目录,并在目录下使用 create umi 来快速的初始化脚手架。

# 使用 npm
npm create umi react-big-screen

如果下载比较慢,可以先配置npm阿里源:

npm config set registry https://registry.npm.taobao.org/

按照 umi 脚手架的引导,第一步先选择 ant-design-pro:

? Select the boilerplate type (Use arrow keys)
> ant-design-pro  - Create project with a layout-only ant-design-pro boilerplate, use together with umi block.
  app             - Create project with a simple boilerplate, support typescript.
  block           - Create a umi block.
  library         - Create a library with umi.
  plugin          - Create a umi plugin.

选择 antd 的版本,4 或者 5,主意V5和V4差别较大,这里我选择了V5。

? Select the boilerplate type ant-design-pro
? � Be the first to experience the new umi@3 ?
> Pro V5
  Pro V4

安装依赖:

cd react-big-screen && cnpm install

2.2、启动脚手架

开启 Umi UI(可选):

npm start

使用admin/ant.design登录系统:

通过npm start我们就可以启动服务,那你可能会好奇执行这个命令为什么就可以启动我们的服务呢?

npm start 运行的是package.json中script块start对应的命令,打开package.json,你会发现我们实际执行的是如下执行:

cross-env是用来解决多环境问题的,你可暂且忽略。重点关注umi dev。

umi  是一个软连接到umi.js的文件:

#!/usr/bin/env node

const resolveCwd = require('resolve-cwd');

const { name, bin } = require('../package.json');
const localCLI = resolveCwd.silent(`${name}/${bin['umi']}`);
if (!process.env.USE_GLOBAL_UMI && localCLI && localCLI !== __filename) {
  const debug = require('@umijs/utils').createDebug('umi:cli');
  debug('Using local install of umi');
  require(localCLI);
} else {
  require('../lib/cli');
}

也就是其实执行的umi/lib/cli.js文件,  关键部分代码:

_asyncToGenerator(function* () {
  try {
    switch (args._[0]) {
      case 'dev':
        const child = (0, _fork.default)({
          scriptPath: require.resolve('./forkedDev')
        }); // ref:
        // http://nodejs.cn/api/process/signal_events.html
        // https://lisk.io/blog/development/why-we-stopped-using-npm-start-child-processes

        process.on('SIGINT', () => {
          child.kill('SIGINT'); // ref:
          // https://github.com/umijs/umi/issues/6009

          process.exit(0);
        });
        process.on('SIGTERM', () => {
          child.kill('SIGTERM');
          process.exit(1);
        });
        break;

      default:
        const name = args._[0];

        if (name === 'build') {
          process.env.NODE_ENV = 'production';
        }

        yield new _ServiceWithBuiltIn.Service({
          cwd: (0, _getCwd.default)(),
          pkg: (0, _getPkg.default)(process.cwd())
        }).run({
          name,
          args
        });
        break;
    }
  } catch (e) {
    console.error(_utils().chalk.red(e.message));
    console.error(e.stack);
    process.exit(1);
  }
})();

三、页面布局调整

运行程序,我们发现菜单默认是位于侧边栏的,如果我们想将菜单是置于顶部的。因此我们需要进行布局的修改。

在修改之前,我们了解到为了降低研发成本,Ant Design Pro将布局通过umi插件 @umijs/plugin-layout 的方式内置,我们只需要通过简单的配置就可以修改Ant Design的Layout,包括导航以及侧边栏。

plugin-layout 插件主要提供了以下功能:

  • 默认为 Ant Design 的 Layout @ant-design/pro-layout,支持它全部配置项。
  • 侧边栏菜单数据根据路由中的配置自动生成。
  • 默认支持对路由的 403/404 处理和 Error Boundary。
  • 搭配 @umijs/plugin-access 插件一起使用,可以完成对路由权限的控制。
  • 搭配 @umijs/plugin-initial-state 插件和 @umijs/plugin-model 插件一起使用,可以拥有默认用户登陆信息的展示。

3.1、修改config/config.ts

可以通过修改配置文件配置 layout的主题、菜单等样式, 修改config/config.ts:

import { Settings as LayoutSettings } from '@ant-design/pro-layout';

//pro-layout布局配置重写,更多参数配置参考;https://github.com/ant-design/ant-design-pro-layout
const Settings: LayoutSettings & {
  pwa?: boolean;
  logo?: string;
} = {
  navTheme: 'dark',             //整体风格设置 light、dark
  // 拂晓蓝
  primaryColor: '#1890ff',       //主题色
  layout: 'top',                //导航模式 side、top、mix
  contentWidth: 'Fluid',        //内容区域宽度:流式Fluid、定宽Fixed
  fixedHeader: false,           //固定header
  fixSiderbar: true,            //固定侧边测但
  colorWeak: false,             //色弱模式
  title: '报警管理平台',
  pwa: false,
  logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
  iconfontUrl: '',
};

export default Settings;

此时,页面布局如下:

3.2、pro-components

ant-design/pro-layout 已经迁移至 ant-design/pro-components 仓库进行后续的维护,访问 https://procomponent.ant.design/layout 可以了解更多信息。

ProComponents 是基于 Ant Design 而开发的模板组件,提供了更高级别的抽象支持,开箱即用。其中ProComponets封装了页面布局相关的组件,这些组件来自于ant-design/pro-layout ,主要包括ProLayout、PageContainer、GridContent、FooterToolbar、SettingDrawer、SiderMenu、TopNavHeader等。

而我们配置的config/config.ts这些信息,会被这些布局组件所使用。具体实现可以查看源码

3.3、SettingDrawer(只针对V5版本,其他把版本查看官方文档)

从官网下载的代码默认是不支持主题切换的,如果想支持主题切换需要安装以下依赖:

cnpm install   umi-plugin-setting-drawer --save-dev
cnpm install   umi-plugin-antd-theme  --save-dev

最新版本如下:

"umi-plugin-antd-theme": "^2.1.2",
"umi-plugin-setting-drawer": "^1.0.3"

同时需要配置主题相关信息config/theme.config.json:

{
  "theme": [
    {
      "key": "dark",
      "fileName": "dark.css",
      "theme": "dark",
      "modifyVars": {
        "dark": true
      }
    },
    {
      "key": "dust",
      "fileName": "dust.css",
      "modifyVars": {
        "@primary-color": "#F5222D"
      }
    },
    {
      "key": "volcano",
      "fileName": "volcano.css",
      "modifyVars": {
        "@primary-color": "#FA541C"
      }
    },
    {
      "key": "sunset",
      "fileName": "sunset.css",
      "modifyVars": {
        "@primary-color": "#FAAD14"
      }
    },
    {
      "key": "cyan",
      "fileName": "cyan.css",
      "modifyVars": {
        "@primary-color": "#13C2C2"
      }
    },
    {
      "key": "green",
      "fileName": "green.css",
      "modifyVars": {
        "@primary-color": "#52C41A"
      }
    },
    {
      "key": "geekblue",
      "fileName": "geekblue.css",
      "modifyVars": {
        "@primary-color": "#2F54EB"
      }
    },
    {
      "key": "purple",
      "fileName": "purple.css",
      "modifyVars": {
        "@primary-color": "#722ED1"
      }
    },
    {
      "key": "dust",
      "theme": "dark",
      "fileName": "dark-dust.css",
      "modifyVars": {
        "@primary-color": "#F5222D",
        "dark": true
      }
    },
    {
      "key": "volcano",
      "theme": "dark",
      "fileName": "dark-volcano.css",
      "modifyVars": {
        "@primary-color": "#FA541C",
        "dark": true
      }
    },
    {
      "key": "sunset",
      "theme": "dark",
      "fileName": "dark-sunset.css",
      "modifyVars": {
        "@primary-color": "#FAAD14",
        "dark": true
      }
    },
    {
      "key": "cyan",
      "theme": "dark",
      "fileName": "dark-cyan.css",
      "modifyVars": {
        "@primary-color": "#13C2C2",
        "dark": true
      }
    },
    {
      "key": "green",
      "theme": "dark",
      "fileName": "dark-green.css",
      "modifyVars": {
        "@primary-color": "#52C41A",
        "dark": true
      }
    },
    {
      "key": "geekblue",
      "theme": "dark",
      "fileName": "dark-geekblue.css",
      "modifyVars": {
        "@primary-color": "#2F54EB",
        "dark": true
      }
    },
    {
      "key": "purple",
      "theme": "dark",
      "fileName": "dark-purple.css",
      "modifyVars": {
        "@primary-color": "#722ED1",
        "dark": true
      }
    }
  ],
  "min": true,
  "isModule": true,
  "ignoreAntd": false,
  "ignoreProLayout": false,
  "cache": true,
  "extraLibraries": [
    "@ant-design/pro-form",
    "@ant-design/pro-descriptions",
    "@ant-design/pro-field",
    "@ant-design/pro-list",
    "@ant-design/pro-card",
    "@ant-design/pro-provider",
    "@ant-design/pro-skeleton",
    "@ant-design/pro-utils"
  ]
}
  • extraLibraries:@antd-design/pro-xxx ;  antd-pro-merge-less 在生成样式文件时,会加载该指定包中样式文件(特别需要注意的是antd-pro-merge-less 默认只加载了antd pro组件 @ant-design/pro-table、@ant-design/pro-form的样式);

  • ignoreAntd:在生成样式文件时,是否加载antd样式文件

  • cache:是否开启缓存,通过比较样式和antd-pro-merge-less/.temp/temp.less文件的hash,判断文件是否需要重新生成,这样就不会在每次启动的时候重新生成样式文件

umi-plugin-antd-theme会根据遍历这个配置文件,动态的在node_modules.plugin-theme heme下生成的对应的样式文件。

最终在点击SettingDrawer组件进行样式调整的时候,实际就是给动态body加入样式文件的过程 :

<link type="text/css" rel="stylesheet" id="theme-style" href="/theme/volcano.css">

我们也可以将样式文件复制到/public/theme/下。

如果我们不想使用SettingDrawer组件,需要把package.json devDependencies中的这两个包排除。

SettingDrawer组件中的主题色ThemeColor 用于展示当前可选的主色,色彩列表themeList由 umi-plugin-antd-theme 插件提供,该插件会将一个色彩列表放到 window.umi_plugin_ant_themeVar 变量上,SettingDrawer 会读取这个变量并给到 ThemeColor。

<ThemeColor
  value={primaryColor}
  colors={
    hideColors
      ? []
      : themeList.colorList[navTheme === 'realDark' ? 'dark' : 'light']
  }
  formatMessage={formatMessage}
  onChange={(color) =>
    changeSetting('primaryColor', color, hideLoading)
  }
/>

3.4、 umi-plugin-antd-theme 插件流程分析

这里只粗略介绍一下具体流程、不做细致的代码分析,感兴趣的请阅读源码。

  1. 更改 cssLoader 配置,修改 src 目录下 less 文件 CSS Modules 选择器名称

  2. 加载工程固定路径下主题配置文件 config/theme.config.json覆盖默认配置(配置配置为空,也就是使用组件原始配置样式)

  3. 设置dev环境临时主题色文件路径为 node_modules/.plugin-theme/theme

  4. 设置serve-static中间件,允许访问临时主题色文件

  5. 将主题色配置信息挂载在 window.umi_plugin_ant_themeVar

  6. dev环境 onDevCompileDone

如果存在临时文件,则删除

创建 .plugin-theme/theme

遍历config/theme.config.json下每个主题色,使用 antd-pro-merge-less包 buildCss 至 .plugin-theme/theme 每一个主题色,生成一个 css 文件 prod 环境与 dev 环境的差异体现在生产的文件生成在 dist/theme 目录下

3.5、antd-pro-merge-less 插件流程分析

  1. 设置临时文件目录antd-pro-merge-less/.temp

  2. 使用 glob 找到 antd-pro-merge-less 项目下所有的 less 文件路径

  3. 将所有less文件内容写入antd-pro-merge-less/.temp/temp.less以及antd-pro-merge-less/.temp/pro.less

  4. 如果config/theme.config.json配置"ignoreAntd": false,则还会导入antd模块下的所有less文件(需要注意的是如果没有配置dark:true, 不会加载themes/dark.less,themes/compack.less),到 ./antd.less

  5. 将@import './antd'导入./components.less文件,同时还会根据extraLibraries配置导入antd design pro组件的样式文件

  6. 提取import文件的变量,删除 import ,写入antd-pro-merge-less/.temp/pro.less ,并引用 @import './components'

  7. 将依赖的 antd 相关组件的 less 文件写入./components.less 开始遍历不同的颜色配置,根据不同配置生成 antd less 文件

  8. 使用 less 将 pro.less 文件转化为 css 文件,并替换变量

常见错误处理:

1、如果运行出现错误 检查node_modules\_antd-pro-merge-less@xxx@antd-pro-merge-lessloopAllLess.js文件,修改文件最后为:

return Promise.resolve(
// prettier.format(content, {
//   parser: 'less',
// }),
content
);

因为prettier.format在格式化less文件时会将 :

@pro-global-footer-prefix-cls: ~'@{ant-prefix}-pro-global-footer'
转为 
@pro-global-footer-prefix-cls ~'@
{ant-prefix}-pro-global-footer'

2、@ant-deisign/pro-card组件存在Bug  _@ant-design_pro-card@1.11.7@@ant-designpro-cardescomponentsStatisticindex.less

.@{ant-prefix}-statistic-title {
color: @text-color;  # 将之前的固定色rgba(00,00,00,0.45)修改成变量
}

3、强制重新生成样式文件

  • 如果config/theme.config.json配置中开启了缓存cache,如果想强制重新生成样式文件,删除antd-pro-merge-less.temp emp.less文件,重新运行
  • 设置cache为false关闭缓存

4、antd pro V5 中,css文件重复打包

直接在V5的Demo页面多次点击菜单来切换路由,会发现样式会有多次覆盖的情况。
https://preview.pro.ant.design/dashboard/analysis?fixSiderbar=true&colorWeak=false&pwa=false
image

目前是参考 https://umijs.org/zh-CN/config#chainwebpack ,修改了打包规则,config.ts中增加以下配置,解决了这个问题:

chunks: ['vendors', 'umi'],
  chainWebpack: function (config, { webpack }) {
    config.merge({
      optimization: {
        splitChunks: {
          chunks: 'all',
          minChunks: 2,
          automaticNameDelimiter: '.',
          cacheGroups: {
            vendor: {
              name: 'vendors',
              test({ resource }) {
                return /[\/]node_modules[\/]/.test(resource);
              },
              priority: 10,
            },
          },
        },
      },
    });
  },

四、新增页面步骤

这里的『页面』指配置了路由,能够通过链接直接访问的模块,要新建一个页面,通常只需要在脚手架的基础上进行简单的配置。

4.1、新增js、less文件

在src/pages下创建新的js、less文件。如果又多个相关页面,可以创建一个新文件夹来放置相关文件。

config
src
  models
  pages
+   NewPage.js
+   NewPage.less
  ...
...
package.json

为了更好的演示,我们初始化NewPage.js的内容如下:

export default () => {
  return <div>New Page</div>;
};

暂时不向本文档中的样式文件添加内容,您也可以尝试自己添加内容。 样式文件默认使用CSS Modules,如果需要,可以导入antd less 变量 在文件的头部:

@import '~antd/lib/style/themes/default.less';

这样可以轻松获取 antd 样式变量并在文件中使用它们,这可以保持保持页面的一致性,并有助于实现自定义主题。

4.2、将文件加入菜单和路由

加入菜单和路由的方式请参照 路由和菜单 - 添加路由/菜单 中的介绍完成。加好后,访问 http://localhost:8000/#/new 就可以看到新增的页面了。

4.3、新增model、service

布局及路由都配置好之后,回到之前新建的 NewPage.js,可以开始写业务代码了!

如果需要用到 dva 中的数据流,还需要在 src/models src/services 中建立相应的 model 和 service,具体可以参考脚手架内置页面的写法。不过我们更加推荐使用轻量数据流管理数据,并且使用 openAPI 插件来生成 serves。

五、代码下载

5.1、开发计划

接下来我将花一段时间开发一个监控平台:

 

5.2、代码下载

代码下载地址:https://github.com/Zhengyang550/react-big-screen

原文地址:https://www.cnblogs.com/zyly/p/14388706.html