zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    设计模式:迭代器模式(Iterator Pattern) 明
    设计模式:目录导航 明
    设计模式:状态模式(State Pattern) 明
    设计模式:命令模式(Command Pattern) 明
    二维DP—— POJ 3186
    COM组件里自动化接口与普通接口的转换
    贪心好题——poj3044
    三分查找——POJ3301
    静态链表+DFS——poj 3272
    妙用队列优化——校赛题
  • 原文地址:https://www.cnblogs.com/zyly/p/14388706.html
Copyright © 2011-2022 走看看