1. Umi介绍 1.1 Umi是什么 https://umijs.org/zh-CN/docs
一套可插拔的企业级 react 应用框架,由dva作者 sorrycc 完成,它既是一个框架也是一个工具。
Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
他在Umi中引入了 UI 工具 antd,打包工具 roadhog,路由 react-router和状态管理器 dva,做到了可插拔机制。通过约定、自动生成和解析代码等方式来辅助开发,减少开发者要写的代码量。
它主要具备以下功能:
可扩展 ,Umi 实现了完整的生命周期,并使其插件化,Umi 内部功能也全由插件完成。此外还支持插件和插件集,以满足功能和垂直域的分层需求。
开箱即用 ,Umi 内置了路由、构建、部署、测试等,仅需一个依赖即可上手开发。并且还提供针对 React 的集成插件集,内涵丰富的功能,可满足日常 80% 的开发需求。
企业级 ,经蚂蚁内部 3000+ 项目以及阿里、优酷、网易、飞猪、口碑等公司项目的验证,值得信赖。
大量自研 ,包含微前端、组件打包、文档工具、请求库、hooks 库、数据流等,满足日常项目的周边需求。
完备路由 ,同时支持配置式路由和约定式路由,同时保持功能的完备性,比如动态路由、嵌套路由、权限路由等等。
面向未来 ,在满足需求的同时,我们也不会停止对新技术的探索。比如 dll 提速、modern mode、webpack@5、自动化 external、bundler less 等等。
为什么不是?create-react-app
create-react-app 是基于 webpack 的打包层方案,包含 build、dev、lint 等,他在打包层把体验做到了极致,但是不包含路由,不是框架,也不支持配置。所以,如果大家想基于他修改部分配置,或者希望在打包层之外也做技术收敛时,就会遇到困难。
什么时候不用 umi?
如果你,
需要支持 IE 8 或更低版本的浏览器
需要支持 React 16.8.0 以下的 React
需要跑在 Node 10 以下的环境中
有很强的 webpack 自定义需求和主观意愿
需要选择不同的路由方案
Umi 可能不适合你。
1.2 Umi 如何工作 写写 Umi 背后的思考和重要概念。
1.2.1 技术收敛
这张图是给内部框架 Bigfish 画的,套到 Umi 上同样合适。他把大家常用的技术栈进行整理,收敛到一起,让大家只用 Umi 就可以完成 80% 的日常工作。
1.2.2 插件和插件集
Umi 支持插件和插件集,通过这张图应该很好理解到他们的关系,通过插件集我们把插件收敛依赖然后支持不同的业务类型。
比如@umijs/preset-react就是一个Umi的插件集,里面整合了一些常用功能。
1.2.3 配置式路由和约定式路由 Umi 的路由既支持配置式,又支持约定式。配置式是对于现实的低头,也是大部分用户在用的,因为他功能强大;约定式是我们希望走去的方向,因为他简洁优雅。
1.2.4 .umi 临时文件 .umi 临时目录是整个 Umi 项目的发动机,你的入口文件、路由等等都在这里,这些是由 umi 内部插件及三方插件生成的。
你通常会在 .umi 下看到以下目录,
1 2 3 4 5 + .umi + core # 内部插件生成 + pluginA # 外部插件生成 + presetB # 外部插件生成 + umi.ts # 入口文件
临时文件是 Umi 框架中非常重要的一部分,框架或插件会根据你的代码生成临时文件,这些原来需要放在项目里的脏乱差的部分都被藏在了这里。
你可以在这里调试代码,但不要在 .git 仓库里提交他,因为他的临时性,每次启动 umi 时都会被删除并重新生成。
2. Umi3的使用 2.1 安装Umi创建项目 1 2 3 $ yarn global add umi@3.2 .23 $ umi -v 3.2 .23
1 2 $ mkdir myapp && cd myapp $ yarn create umi
2.2 升级到Umi3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 1. 删除package.json 中 dva 和 antd 的依赖 (Umi3 中已经包含这两个依赖)2. 修改package.json 中umi的包的版本号 "umi" : "^3.0.0" , 3. 删除package.json 中 umi-plugin-react 依赖,新增umijs/preset-react 依赖 "@umijs/preset-react" : "^1" , 4. 修改package.json 中的node环境要求 "node" : ">=10.13.0" 5. tsconfig.json 中的paths新增如下: "@@/*" : ["src/.umi/*" ] 6. 修改 .umirc .ts 文件 import { defineConfig } from 'umi' ; let path = require ('path' ); export default defineConfig ({ routes : [ { path : '/' , component : '../layouts/index' , routes : [ { path : '/' , component : '../pages/index' } ] } ], antd : { }, dva : { hmr : true , }, locale : { default : 'zh-CN' , antd : true , baseNavigator : false , }, targets : { ie : 11 }, hash : true , history : { type : 'hash' }, theme : { 'font-size-base' : '34px' }, dynamicImport : {}, base :'/' , publicPath :'./' , define : { }, proxy : { }, alias : { '@' : path.resolve (__dirname, 'src' ) } }) 7. 注释掉 app.ts 中的const dva 代码 8. 修改pages/index.tsx import { useIntl } from 'umi' ; <a href ="https://umijs.org/guide/getting-started.html" > {useIntl().formatMessage({ id: 'index.start' })} </a >
2.3 运行项目
几秒钟后,你会看到以下输出,
1 2 3 4 5 DONE Compiled successfully in 212ms App running at : - Local : http : - Network : http :
在浏览器里打开 http://localhost:8000 ,你会看到 umi 的欢迎界面。
2.4 目录结构介绍
2.5 配置文件介绍 2.5.1 .editorconfig 该文件是配置编辑器的一些设置,这里我修改了一个缩进,indent_size = 4。因为个人比较喜欢4个缩进,看着舒服。
2.5.2 .env 该文件是项目环境配置文件,默认的是BROWSER=none,这时候项目启动后,浏览器不会自动打开。
2.5.3 .eslintrc 多人开发时候,一套良好的代码规范是非常必要的。这里配置了一份基础eslint文件,供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 { "env" : { "es6" : true , "node" : true , "browser" : true , "mocha" : true }, "extends" : [ "eslint:recommended" , "react-app" ], "rules" : { "strict" : "error" , "eqeqeq" : "error" , "no-lone-blocks" : "error" , "no-lonely-if" : "error" , "no-multi-spaces" : "error" , "no-multiple-empty-lines" : [ "error" , { "max" : 2 } ], "no-param-reassign" : "error" , "no-spaced-func" : "error" , "no-use-before-define" : "warn" , "no-unused-vars" : "warn" , "no-with" : "error" , "default-case" : "error" , "key-spacing" : [ "error" , { "beforeColon" : false , "afterColon" : true } ], "comma-spacing" : [ "error" , { "before" : false , "after" : true } ], "generator-star-spacing" : [ "error" , { "before" : true , "after" : false } ], "semi" : [ "warn" , "always" , { "omitLastInOneLineBlock" : true } ], "no-restricted-globals" : "off" , "array-callback-return" : "off" , "no-console" : [ "warn" , { "allow" : [ "info" , "warn" , "error" , "time" , "timeEnd" ] } ], "react/react-in-jsx-scope" : "warn" } }
2.5.3 .umirc .umirc为Umi项目的配置文件,.umirc.js 和 config/config.js二选一
2.5.4 tsconfig.json typescript的配置文件,可以配置ts编译的一些特征
https://www.jianshu.com/p/78dcb09dac2c
1 2 "suppressImplicitAnyIndexErrors" :true ,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 { "compilerOptions" : { "target" : "es5" , "module" : "commonjs" , "lib" : ["es6" , "dom" ] , "allowJs" : true , "checkJs" : true , "jsx" : "preserve" , "declaration" : true , "declarationMap" : true , "sourceMap" : true , "outFile" : "./" , "outDir" : "./" , "rootDir" : "./" , "composite" : true , "incremental" : true , "tsBuildInfoFile" : "./" , "removeComments" : true , "noEmit" : true , "importHelpers" : true , "downlevelIteration" : true , "isolatedModules" : true , "strict" : true , "noImplicitAny" : true , "strictNullChecks" : true , "strictFunctionTypes" : true , "strictBindCallApply" : true , "strictPropertyInitialization" : true , "noImplicitThis" : true , "alwaysStrict" : true , "noUnusedLocals" : true , "noUnusedParameters" : true , "noImplicitReturns" : true , "noFallthroughCasesInSwitch" : true , "moduleResolution" : "node" , "baseUrl" : "./" , "paths" : {}, "rootDirs" : [], "typeRoots" : [], "types" : [], "allowSyntheticDefaultImports" : true , "esModuleInterop" : true , "preserveSymlinks" : true , "sourceRoot" : "" , "mapRoot" : "" , "inlineSourceMap" : true , "inlineSources" : true , "experimentalDecorators" : true "emitDecoratorMetadata" : true , } "files" : [], "include" : [], "exclude" : [], "extends" : "" , "compileOnSave" : true , "references" : [], }
2.6 路由和页面跳转 2.6.1 新建路由 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 $ umi g page product/index --typescript --less create src/pages/product/index.js create src/pages/product/index.less ✔ success routes : [ { path : '/' , component : '../layouts/index' , routes : [ { path : '/product/index' , component : '../pages/product/index' , }, { path : '/' , component : '../pages/index' } ] } ], declare module 'slash2' ; declare module '*.css' ; declare module '*.less' ; declare module '*.scss' ; declare module '*.sass' ; declare module '*.svg' ; declare module '*.png' ; declare module '*.jpg' ; declare module '*.jpeg' ; declare module '*.gif' ; declare module '*.bmp' ; declare module '*.tiff' ; declare module 'omit.js' ;
2.6.2 约定式路由 除配置式路由外,Umi 也支持约定式路由。约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
如果没有 routes 配置,Umi 会进入约定式路由模式 ,然后分析 src/pages
目录拿到路由配置。
比如以下文件结构:
1 2 3 4 . └── pages ├── index.tsx └── users.tsx
会得到以下路由配置,
1 2 3 4 [ { exact: true, path: '/', component: '@/pages/index' }, { exact: true, path: '/users', component: '@/pages/users' }, ]
需要注意的是,满足以下任意规则的文件不会被注册为路由,
以 .
或 _
开头的文件或目录
以 d.ts
结尾的类型定义文件
以 test.ts
、spec.ts
、e2e.ts
结尾的测试文件(适用于 .js
、.jsx
和 .tsx
文件)
components
和 component
目录
utils
和 util
目录
不是 .js
、.jsx
、.ts
或 .tsx
文件
文件内容不包含 JSX 元素
所以当我们把.umirc.js中的routes配置注释掉的时候,访问路径http://localhost:8000/#/product 能够正常展示页面。
2.6.3 路由配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 component : '@/layouts/index' { path : '/one' , exact : true } routes : [ { path : '/list' , component : 'list' }, { path : '/admin' , component : 'admin' }, ], { path : '*' , redirect : '/' }
2.6.4 路由跳转和传参 1 2 3 4 5 6 import { Link } from 'umi' ;export default () => ( <div > <Link to ="/users" > Users Page</Link > </div > );
1 2 3 4 5 6 7 8 9 10 11 12 13 import { history } from 'umi' ;history.push ('/list' ); history.push ('/list?a=b' ); history.push ({ pathname : '/list' , state : { a : 'b' , }, }); history.goBack ();
1 2 3 4 5 6 7 match,当前路由和 url match 后的对象,包含 params、path、url 和 isExact 属性 location,表示应用当前出于哪个位置,包含 pathname、search、query 等属性 history,同 api history 接口 route,当前路由配置,包含 path、exact、component、routes 等 routes,全部路由信息
2.6.5 动态路由参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import React from 'react' ;import styles from './productDetail.less' ;import { RouteComponentProps } from 'react-router-dom' ;interface RouterInfo { id : string ; } export default (props : RouteComponentProps <RouterInfo >) => { return ( <div > <h1 className ={styles.title} > Page productDetail/index ---{props.match.params.id}</h1 > </div > ); }; routes : [ { path : '/' , component : '../layouts/index' , routes : [ { path : '/product/index' , component : '../pages/product/index' , }, { path : '/productDetail/:id' , component : '../pages/productDetail/index/[id]' }, { path : '/' , component : '../pages/index' } ] } ]
2.7 样式和资源文件 2.7.1 全局样式 Umi 中约定 src/global.css
为全局样式,如果存在此文件,会被自动引入到入口文件最前面。
比如用于覆盖样式,
1 2 3 4 .ant-select-selection { max-height : 51px ; overflow : auto; }
2.7.2 CSS Modules Umi 会自动识别 CSS Modules 的使用,你把他当做 CSS Modules 用时才是 CSS Modules。
比如:
1 2 3 4 5 import styles from './foo.css' ;import './foo.css' ;
2.7.3 CSS 预处理器 Umi 内置支持 less,不支持 sass 和 stylus,但如果有需求,可以通过 chainWebpack 配置或者 Umi 插件的形式支持。
2.7.4 ts中使用图片 通过 require 引用相对路径的图片。
比如:
1 export default () => <img src ={require( '. /foo.png ')} />
支持别名,比如通过 @
指向 src 目录:
1 export default () => <img src ={require( '@/foo.png ')} />
2.7.5 CSS 里使用图片 通过相对路径引用。
比如,
1 2 3 .logo { background : url (./foo.png ); }
CSS 里也支持别名,但需要在前面加 ~
前缀,
1 2 3 .logo { background : url (~@/foo.png ); }
注意:
这是 webpack 的规则,如果切到其他打包工具,可能会有变化
less 中同样适用
2.7.6 Base64 编译 通过相对路径引入图片的时候,如果图片小于 10K,会被编译为 Base64,否则会被构建为独立的图片文件。
10K 这个阈值可以通过 inlineLimit 配置 修改。
2.8 国际化 @umijs/plugin-locale是umi中的一个国际化插件,用于解决 i18n 问题。
2.8.1 开启国际化 1 2 3 4 5 6 7 8 9 locale : { default : 'zh-CN' , antd : true , baseNavigator : false , },
2.8.2 约定式多语言支持 比如以下目录,项目就拥有了 zh-CN
与 en-US
国际化语言切换(文件名必须要符合规范):
1 2 3 4 5 + src + locales - zh-CN.ts - en-US.ts + pages
2.8.3 主要API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # getAllLocales 获取当前获得所有国际化文件的列表,默认会在 locales 文件夹下寻找类似 en-US .(js|json|ts) 文件。 import { getAllLocales } from 'umi' ;console .log (getAllLocales ()); # getLocale 将获得当前选择的语言。 import { getLocale } from 'umi' ;console .log (getLocale ()); # useIntl 是最常用的 api,它可以获得 formatMessage 等 api 来进行具体的值绑定。 import { useIntl } from 'umi' ;<a > {useIntl().formatMessage({ id: 'index.start' })} </a > # setLocale 设置切换语言,默认会刷新页面,可以通过设置第二个参数为 false ,来实现无刷新动态切换。 import { setLocale } from 'umi' ;setLocale ('zh-TW' , true );setLocale ('zh-TW' , false );
3.flux和redux 3.1 Flux Flux 是一种架构思想,专门解决软件的结构问题。它跟MVC 架构 是同一类东西,但是更加简单和清晰 。
首先,Flux将一个应用分成四个部分。
View : 视图层
Action (动作):表示数据从应用程序发送到store的有效信息负载。它是 store 数据的唯一 来源。一般来说你会通过 Dispatcher派发器将 action 传到 store。
Dispatcher (派发器):用来接收Actions、执行回调函数
Store (数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面
Flux 的最大特点,就是数据的”单向流动”。
用户访问 View
用户在View中Dispatcher一个Action,要求 Store 进行相应的更新
Store 更新后,发出一个”change”事件
View 收到”change”事件后,更新页面
3.2 Redux http://cn.redux.js.org/
我们把Flux看做一个框架的理念的话,Redux是Flux的一种实现。Redux是SPA单页面应用程序中多个组件之间共享数据的一种方式。
Flux的基本原则是“单向数据流”,Redux在此基础上强调三个基本原则:
1.唯一数据源 :唯一数据源指的是应用的状态数据应该只存储在唯一的一个Store上。 2.保持状态只读 : 保持状态只读,就是说不能去直接修改状态,要修改Store的状态,必须要通过派发一个action对象完成。 3.数据改变只能通过纯函数完成 :这里所说的纯函数就是把Reducer,Reducer描述了state状态如何修改。Redux这个名字的前三个字母Red代表的就是Reducer,其实Redux名字的含义就是Reducer+Flux。
3.3 dva 3.3.1 dva介绍 dva由阿里架构师 sorrycc 带领 team 完成的一套前端框架,dva 是一个基于 redux 和 redux-saga (异步处理)的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch ,所以也可以理解为一个轻量级的应用框架。
特性
易学易用 ,仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用 后更是降低为 0 API
elm 概念 ,通过 reducers, effects 和 subscriptions 组织 model。elm是专注web前端的纯函数式语言。
插件机制 ,比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
支持 HMR ,基于 babel-plugin-dva-hmr 实现 components、routes 和 models 的 HMR
3.3.2 dva的数据流
用户在 视图层 会触发(dispatch)一些动作(action),这些动作会传递到我们的Model.js 这个文件当中,根据action的名字,找到对用的方法(effect 或者 reducer) ,然后更新state,视图重新渲染。
3.3.3 dva核心概念 a) model 其中,model 是 DVA 中最重要的概念,基本的属性如下:
namespace:model 的命名空间,只能用字符串。一个大型应用可能包含多个 model,通过namespace区分。
state:当前 model 状态的初始值,表示当前状态。
reducers:用于处理同步操作,可以修改 state,由 action 触发。
effects:用于处理异步操作(例如:与服务端交互)和业务逻辑,也是由 action 触发。但是,它不可以修改 state,要通过触发 action 调用 reducer 实现对 state 的间接操作。
b) connect 连接dva model和react组件,目的是为了让组件获取model中的数据和驱动model改变的方法。
c) dispatch dispatch 函数就是和 dva model 打交道的唯一途径, dispatch 函数接受一个 action对象 作为入参。action对象需要有type字段
d) action action:一个对象,是 reducers 及 effects 的触发器,通过dispatch来触发,action对象需要有type字段
3.4 dva实战 3.4.1 改造项目结构
3.4.2 mock a) mock介绍 Mock 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API。主要用于项目的开发测试阶段。
https://github.com/nuysoft/Mock/wiki
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #1. 安装mockjs yarn add mockjs --dev #2. 修改.umirc .ts 开启mock mock : {} #3. mock基本使用 import { Request , Response } from 'express' ;import Mock from 'mockjs' ;export default { '/api/test' : { id : 1 , username : 'kenny' , sex : 6 }, 'GET /api/hi' : (req: Request, res: Response ) => { res.json ({ id : 1 , username : 'jaja' , }); }, 'GET /api/mock' : Mock .mock ({ success : true , message : '@cparagraph' , 'list|1-5' : [ { 'sid|+1' : 1 , 'userId|5' : '' , }, ], }), };
b) mock/user.js使用mock提供接口数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import { Request , Response } from 'express' ;import Mock from 'mockjs' ;interface UserType { id : number; name : string; email : string; website : string; } const users : UserType [] = [ { id : 1 , name : 'zhangsan' , email : 'zhangsan@qq.com' , website : 'xx.com' }, { id : 2 , name : 'lisi' , email : 'lisi@qq.com' , website : 'll.com' }, ]; export default { 'GET /api/users' : (req: Request, res: Response ) => { var page = req.query .page ; var data = { list : users, total : 105 , page : page }; res.json (data); }, '/api/users/:id' : (req: Request, res: Response ) => { const id = parseInt (req.params .id ); const u = users.find ((item ) => { if (item.id === id) { return item; } }); res.json (u); }, '/api/users/delete/:id' : (req: Request, res: Response ) => { const id = parseInt (req.params .id ); const index = users.findIndex ((item ) => { if (item.id == id) { return true ; } }); users.splice (index, 1 ); res.json ({ status : 200 , message : '删除成功' , }); }, 'POST /api/users/create' : (req: Request, res: Response ) => { res.end ('OK' ); }, };
3.4.3 请求的封装 a) fetch和umi-request fetch 是es6中推出的一种可替代ajax获取/提交数据的技术,有些高级浏览器已经可以使用window.fecth使用了。相比于使用jQuery.ajax更轻量,而且它支持Promise,更加符合现在的编程习惯。但是,一定记住fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
umi-request 是基于 fetch 封装的开源 http 请求库,旨在为开发者提供一个统一的 API 调用方式,同时简化使用方式,提供了请求层常用的功能:
URL 参数自动序列化
POST 数据提交方式简化
Response 返回处理简化
请求超时处理
请求缓存支持
GBK 编码处理
统一的错误处理方式
请求取消支持
Node 环境 http 请求
拦截器机制
洋葱中间件机制
umi-request和fetch和axios区别
b) app.ts运行时配置 运行时配置文件,可以在这里扩展运行时的能力,比如修改路由、修改 render 方法等。
Umi约定 src/app.tsx
为运行时配置。比如我们可以再在 src/app.ts
中配置一些运行时的配置项来实现部分自定义需求。示例配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import { notification } from 'antd' ;import { RequestConfig } from 'umi' ;import { ResponseError } from 'umi-request' ;const errorHandler = (error: ResponseError ) => { notification.error ({ description : '您的网络发生异常,无法连接服务器' , message : '网络异常' , }); throw error; }; export const request : RequestConfig = { errorHandler, requestInterceptors : [ (url, options ) => { console .log ('请求拦截器' ); return { url, options, }; }, ], responseInterceptors : [ (response, options ) => { console .log ('响应拦截器' ) return response; }, ], };
c) 封装service 1 2 3 4 5 6 import { request } from 'umi' ;export function queryUserList ({ page = 1 } ) { return request (`/api/users?page=${page} ` ); }
3.4.4 新建页面 发送请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 umi g page user/index --typescript --less routes : [ { path : '/' , component : '../layouts/index' , routes : [ { path : '/' , component : '../pages/index' }, { path : '/user' , component : '../pages/user/index' } ] } ], import React , { useEffect } from 'react' ;import styles from './index.less' ;import { queryUserList } from './services' ;export default () => { useEffect (() => { getUsers (); }, []); const getUsers = async ( ) => { let result = await queryUserList ({ page : 1 }); console .log (result); }; return ( <div > <h1 className ={styles.title} > Page user/index</h1 > </div > ); };
3.4.5 编写dva model dva 通过 model
的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
好处在于可以统一管理模型数据和把模型数据共享到组件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import { Effect , Reducer , Subscription , Action } from 'umi' ;import { queryUserList } from '../services' ;export interface UserItem { id : number; name : string; email : string; website : string; } export interface UserModelState { list?: UserItem []; total?: number; page?: number; } export interface UserModelType { namespace : 'user' ; state : UserModelState ; effects : { fetchUserList : Effect ; removeUserById?: Effect ; }; reducers : { save : Reducer <UserModelState >; removeById?: Reducer <UserModelState >; }; subscriptions?: { setup : Subscription }; } const UserModel : UserModelType = { namespace : 'user' , state : { list : [], total : 0 , page : 0 , }, reducers : { save (state, { payload } ) { const { list, total, page } = payload; return { ...state, list, total, page }; } }, effects : { *fetchUserList ({ payload: { page = 1 } }, { call, put } ) { const { list, total } = yield call (queryUserList, { page }); yield put ({ type : 'save' , payload : { list : list, total : parseInt (total, 10 ), page : parseInt (page, 10 ), }, }); } } }; export default UserModel ;
3.4.6 编写页面 到这里,我们已经单独完成了 model 和 页面,那么他们如何串联起来呢?
dva 提供了 connect
方法。如果你熟悉 redux,这个 connect 来自 react-redux。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import React , { FC , useEffect } from 'react' ;import { ConnectProps , Loading , connect } from 'umi' ;import { UserModelState } from './models' ;interface PageProps extends ConnectProps { user : UserModelState ; loading : boolean ; } const UserPage : FC <PageProps > = ({ user, dispatch, loading } ) => { const { list,total } = user; useEffect (() => { dispatch && dispatch ({ type : 'user/fetchUserList' , payload : { page : 1 , }, }); }, []); return <div > Hello {JSON.stringify(list)}</div > ; }; export default connect (({ user, loading }: { user: UserModelState; loading: Loading } ) => ({ user, loading : loading.models .user , }))(UserPage );
3.4.7 使用订阅更新设置dva数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ......... subscriptions : { setup ({ dispatch, history } ) { return history.listen (({ pathname } ) => { if (pathname === '/user' ) { dispatch ({ type : 'fetchUserList' , payload : { page : 1 , } }); } }); }, }
3.4.8 使用ant design编写页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 import React , { FC ,useEffect,useState } from 'react' ;import { Table , Pagination , Popconfirm , Button } from 'antd' ;import { ConnectProps , Loading , connect } from 'umi' ;import { UserModelState ,UserItem } from './models' ;interface PageProps extends ConnectProps { user : UserModelState ; loading : boolean; } const UserPage : FC <PageProps > = ({ user, dispatch, loading } ) => { const { list, total, page } = user; function onChangeUserPagination (page: number ) { dispatch && dispatch ({ type : 'user/fetchUserList' , payload : { page : page, }, }); } useEffect (() => { dispatch && dispatch ({ type : 'user/fetchUserList' , payload : { page : 1 , }, }); }, []); const toggle = ( )=>{ setPopShow (true ) } const columns = [ { title : '用户名' , dataIndex : 'name' , key : 'name' , }, { title : '电子邮件' , dataIndex : 'email' , key : 'email' , }, { title : '个人网站' , dataIndex : 'website' , key : 'website' , }, { title : '操作' , key : 'operation' , render : (item: UserItem ) => ( <span > <Button > 编辑</Button > <Popconfirm visible ={popShow} title ="Confirm to delete?" > <Button > 删除</Button > </Popconfirm > </span > ), }, ]; return ( <div > <div > <Table loading ={loading} //表格的加载状态 columns ={columns} //表格列信息 dataSource ={list} //表格数据源 rowKey ={(record:UserItem) => record.id} //表格每一行数据的key pagination={false} //不显示表格默认的分页器 /> <Pagination className ="ant-table-pagination" total ={total} // 数据总数 current ={page} // 当前页数 onChange ={onChangeUserPagination} //改变页码的点击事件 pageSize ={10} //每一页的数量 /> </div > </div > ); }; export default connect (({ user, loading }: { user: UserModelState; loading: Loading } ) => ({ user, loading : loading.models .user , }))(UserPage );
3.4.9 完成删除功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 export function deleteUserById (id: number ) { return request (`/api/users/delete/${id} ` ); } import { queryUserList, deleteUserById } from '../services' ;removeById (state, { payload } ) { const { id } = payload; var filterList = state && state.list && state.list .filter ((item ) => { if (item.id !== id) { return item; } }); return { ...state, ...{ list : filterList } }; }, *removeUserById ({ payload: { id } }, { call, put } ) { const result = yield call (deleteUserById, id); yield put ({ type : 'removeById' , payload : { id : id, }, }); }, # 页面新增删除事件 <Popconfirm visible={popShow} title="Confirm to delete?" onConfirm={()=> deleteHandler (item.id )}> <Button onClick ={() => {toggle()}}>删除</Button > </Popconfirm > const [popShow,setPopShow] = useState (false ) const deleteHandler = (id:number )=>{ setPopShow (false ) dispatch && dispatch ({ type : 'user/removeUserById' , payload : { id, }, }); } const toggle = ( )=>{ setPopShow (true ) }