本人也是近期开始接触的umi3的,看了很多文档,自己也做了些总结,本文也主要借鉴了官方文档,还有一篇语雀上的教程
umi3整合 dva 数据流,在 umi@3 中要使用 dva 的功能很简单,只要使用安装 @umijs/plugin-dva
并在 配置文件中开启 dva
配置。
$ yarn add @umijs/plugin-dva
# npm install @umijs/plugin-dva --save
如果你是通过脚手架创建的项目,则不用执行上面的步骤!
在config/config.ts中配置就可以了:
export default defineConfig({
...
dva: {
hmr: true,//表示是否启用 dva model 的热更新。
immer: true//表示是否启用 immer 以方便修改 reducer
},
...
})
介绍
包含以下功能,
^2.6.0-beta.20
,如果项目中有依赖,会优先使用项目中依赖的版本。loading
字段使用即可immer
开启约定式的 model 组织方式
符合以下规则的文件会被认为是 model 文件,
src/models
下的文件src/pages
下,子目录中 models 目录下的文件src/pages
下,所有 model.ts 文件比如:
+ src
+ models/a.ts
+ pages
+ foo/models/b.ts
+ bar/model.ts
其中 a.ts
,b.ts
和 model.ts
如果其内容是有效 dva model 写法,则会被认为是 model 文件。
dva model 校验
默认,上一小节的找到的文件,会做一次校验,校验通过后,才会被添加到最终到 dva model 列表。
一些示例,
// 通过
export default { namespace: 'foo' };
export default { reducers: 'foo' };
// 通过
const model = { namespace: 'foo' };
export default model;
// 通过,支持 dva-model-extend
import dvaModelExtend from 'dva-model-extend';
export default dvaModelExtend(baseModel, {
namespace: 'foo',
});
// 通过
export default { namespace: 'foo' };
// 不通过
export default { foo: 'bar' };
配置
比如:
export default {
dva: {
immer: true,
hmr: false,
},
}
skipModelValidate
boolean
false
是否跳过 model 验证。
extraModels
string[]
[]
配置额外到 dva model。
immer
boolean
false
表示是否启用 immer 以方便修改 reducer。
hmr
boolean
false
表示是否启用 dva model 的热更新。
umi 接口
常用方法可从 umi 直接 import。
比如:
import { connect } from 'umi';
接口包含,
绑定数据到组件。
获取 dva 实例,即之前的 window.g_app
。
hooks 的方式获取 dispatch,dva 为 2.6.x 时有效。
hooks 的方式获取部分数据,dva 为 2.6.x 时有效。
hooks 的方式获取 store,dva 为 2.6.x 时有效。
命令
查看项目中包含了哪些 model。
$ umi dva list model
类型
通过 umi 导出类型:ConnectRC
,ConnectProps
,Dispatch
,Action
,Reducer
,ImmerReducer
,Effect
,Subscription
,和所有 model
文件中导出的类型。
model 用例
import { Effect, ImmerReducer, Reducer, Subscription } from 'umi';
export interface IndexModelState {
name: string;
}
export interface IndexModelType {
namespace: 'index';
state: IndexModelState;
effects: {
query: Effect;
};
reducers: {
save: Reducer;
// 启用 immer 之后
// save: ImmerReducer;
};
subscriptions: { setup: Subscription };
}
const IndexModel: IndexModelType = {
namespace: 'index',
state: {
name: '',
},
effects: {
*query({ payload }, { call, put }) {
},
},
reducers: {
save(state, action) {
return {
...state,
...action.payload,
};
},
// 启用 immer 之后
// save(state, action) {
// state.name = action.payload;
// },
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname }) => {
if (pathname === '/') {
dispatch({
type: 'query',
})
}
});
}
}
};
export default IndexModel;
page 用例
import React, { FC } from 'react';
import { IndexModelState, ConnectProps, Loading, connect } from 'umi';
interface PageProps extends ConnectProps {
index: IndexModelState;
loading: boolean;
}
const IndexPage: FC = ({ index, dispatch }) => {
const { name } = index;
return Hello {name};
};
export default connect(({ index, loading }: { index: IndexModelState; loading: Loading }) => ({
index,
loading: loading.models.index,
}))(IndexPage);
或者
import React from 'react';
import { IndexModelState, ConnectRC, Loading, connect } from 'umi';
interface PageProps {
index: IndexModelState;
loading: boolean;
}
const IndexPage: ConnectRC = ({ index, dispatch }) => {
const { name } = index;
return Hello {name};
};
export default connect(({ index, loading }: { index: IndexModelState; loading: Loading }) => ({
index,
loading: loading.models.index,
}))(IndexPage);
FAQ
检查:
umi dva list model
检查,或者执行 umi g tmp
后查看 src/.umi/plugin-dva/dva.ts
中检查 model 注册情况以及 tsconfig.json 等定义问题,参考 FAQ#import from umi 没有定义怎么办?
配置 dva: { skipModelValidate: true }
关闭 dva 的 model 校验。
在 umi 项目中并没有规定一定要使用某种 http 请求方式,我们可以根据实际项目,选择自己最熟悉和服务端交互最优的 http 请求方式,一般项目中我们比较常用的就是 fetch 和 axios ,这两者的优缺点比较,可以查阅其他资料,但是无论你选择哪一种,在你需要更换成另一种时,只需要修改几行代码,因为它们在使用上,只有一点点参数结构的差异。
umi中集成了umi-request插件,所以我就选择了使用umi-request
废话不多说,直接看例子,在service.ts
中这么写
import request from '@/utils/request';
import { MoreListParams } from './data.d'
export async function getChannelData (params?:MoreListParams){
return request(`api/getChannelData`,{
method:'GET',
data:{...params}
})
}
在model.ts
这么调用
import { Effect, Reducer } from 'umi';
import { getChannelData } from './_service';
import { MoreListItem } from './data.d'
export interface MoreModelState {
listData: MoreListItem[];
}
export interface MoreModelType {
namespace: 'more';
state: MoreModelState;
effects: {
getListdata: Effect;
};
reducers: {
channelData: Reducer<{}>;
};
}
const MoreModel: MoreModelType = {
namespace: 'more',
state: {
listData: []
},
effects: {
//这里调用请求
*getListdata({ payload }, { call, put }) {
const response = yield call(getChannelData, payload);
yield put({
type: 'channelData',
payload: response
})
}
},
reducers: {
channelData(state, action) {
const datas = action.payload.data;
return {
...state,
listData:[...datas]
};
}
}
}
export default MoreModel;
很多人可能会疑惑,你那个service.js
中应用的那个request
是怎么设置的呢?如果你是通过脚手架创建的项目,在你的src/utils
的目录下有个,request.ts
的文件,其中是umi给你集成好的(当然,你也可以自己修改):
/**
* request 网络请求工具
* 更详细的 api 文档: https://github.com/umijs/umi-request
*/
import { extend } from 'umi-request';
import { notification } from 'antd';
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
/**
* 异常处理程序
*/
const errorHandler = (error: { response: Response }): Response => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
} else if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}
return response;
};
/**
* 配置request请求时的默认参数
*/
const request = extend({
errorHandler, // 默认错误处理
credentials: 'include', // 默认请求是否带上cookie
});
export default request;
关于proxy的问题,我相信熟悉vue的同学应该都遇到这个,在前后端联调的时候,经常会遇到跨域的问题,这个时候我们的解决办法就是在vue.config.js文件中配置devServer来解决跨域的问题!
详细的 proxy 技术可以查阅其他资料,这里简要说明下:之所以会出现跨域访问问题,是因为浏览器的安全策略。所以我们预想是不是有一种方式,能够绕过浏览器的安全策略?
那就是先请求一个同源服务器,再由服务器去请求其他的服务器。比如:
https://pvp.qq.com
服务器,但是它存在跨域。http://localhost:3000
(假设的),它不存在跨域问题,所以它受理了我们的请求,并且我们可以取得它返回的数据。http://localhost:3000
返回的数据,又是从真实的 https://pvp.qq.com
获取来的,因为服务端不是在浏览器环境,所以就没有浏览器的安全策略问题。http://localhost:3000
(假设的)这个服务器,它只是把我们请求的参数,转发到真实服务端,又把真实服务端下发的数据,转发给我们,所以我们称它为代理。umi 提供了 proxy 来处理这个问题。
配置proxy.ts
‘/api/’ 设置了代理的请求头,比如这里定义了 /api
,当你访问如 /api/abc
这样子的请求,就会触发代理
target 设置代理的目标,即真实的服务器地址
changeOrigin 设置是否跨域请求资源
pathRewrite 表示是否重写请求地址,比如这里的配置,就是把 /api
替换成空字符
在实际项目开发中,我们经常会遇到和服务端同步开发的情况。这时候我们就可以要求服务端先输出接口文档,前后端都根据接口文档输出。在项目交付之前一段时间,再进行前后端连调。
在demo开发学习的过程中,我们也常常遇到需要静态数据的情况
公司网络限制了只能访问部分外网,是的,墙里墙,还是满多的
没有网络的情况,呵呵,想到咖啡店敲个代码,悠哉悠哉,没有公用Wi-Fi,接口都挂了,页面渲染不出来
umi 中使用 mock
Umi 约定 /mock
文件夹下所有文件为 mock 文件。
比如:
.
├── mock
├── api.ts
└── users.ts
└── src
└── pages
└── index.tsx
/mock
下的 api.ts
和 users.ts
会被解析为 mock 文件。
如果 /mock/api.ts
的内容如下,
export default {
// 支持值为 Object 和 Array
'GET /api/users': { users: [1, 2] },
// GET 可忽略
'/api/users/1': { id: 1 },
// 支持自定义函数,API 参考 express@4
'POST /api/users/create': (req, res) => {
// 添加跨域请求头
res.setHeader('Access-Control-Allow-Origin', '*');
res.end('ok');
},
}
然后访问 /api/users
就能得到 { users: [1,2] }
的响应,其他以此类推。
详见配置#mock。
可以通过配置关闭,
export default {
mock: false,
};
也可以通过环境变量临时关闭,
$ MOCK=none umi dev
你可以在对应的页面文件夹下,例如下图就是在/pages/more
目录下创建了一个_mock.ts
的文件,也可以达到mock数据的效果
代码:
import { Request, Response } from 'express';
import { MoreListItem } from './data.d'
const channelTaleData:MoreListItem[] = [];
for(let i = 0; i < 20 ; i++){
channelTaleData.push({
id:i,
name:`名字${i}`,
age:i,
city:`城市${i}`
})
}
const searchChannelData = (name?:string) => {
let result:MoreListItem[]
if(name){
result = channelTaleData.filter(item => {
return item.name.indexOf(name) > -1;
})
}else{
result = channelTaleData;
}
return result;
}
export default{
'GET /api/getChannelData':{
data:[...channelTaleData]
},
'POST /api/searchChannelData':(req: Request, res: Response) => {
res.send({
status:'ok',
data:searchChannelData(req.body.name),
})
}
}
到此这个低配版的umi3的教程就结束了,
当然了,本人也在不断的踩坑中,无论是typescript,react,umi3…还有很多的坑要踩。文中有什么不妥之处,也欢迎广大网友,批评指正,如有需要提供某一模块更详细的教程的,欢迎留言。