接口说明
项目接口
项目接口: http://leju.bufan.cloud/swagger-ui.html
服务器host: http://leju.bufan.cloud
接口说明,当前接口为不凡学院后期项目”乐居商城”项目接口. 在中后台项目开发中,力争采用真实项目接口,模拟真实开发情景,以达到学以致用的目的. 在这里可以采用该接口进行测试.
登录接口
登录接口: /lejuAdmin/index/login , 请求类型: post.
react网络请求的逻辑分层
在antd项目中, 与服务器数据交互的逻辑和 vue-element-admin中的方式有很大的区别. vue是一个mv-vm框架,主要是视图(view)和数据模型(model)之间的数据交互,网络请求采用axios,也可以在vue的实例方法中进行.
而antd项目推荐的交互模式是 视图(**page**
/view) 要用**model**
进行状态(数据model,类似于vue中的data)管理,**service**
是对接口的封装,网络请求采用fetch. **page **
可以调用model中的”方法”,在 **model**
中可以调用 **service**
中的异步接口,进而实现更新**page**
的需求. 所以这是一种三层结构.
Dva简介
什么是dva
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
Dva与vuex的区别
Dva的状态管理有点类似于vuex,但有区别:
区别:
- vuex由state(状态), mutations(同步方法),actions(异步方法)组成
- dva由state(状态), reducers(同步方法), effects(异步方法)组成
- vuex中通过commit调用mutation, 通过dispatch调用action,都可以传递参数payload(载荷)
- dva中通过dispatch调用reducer和effect,都可以传递参数payload(载荷)
- vuex中在mutation中可以单独修改state的某个属性,在dva中,reducer中必须返回一个新的完整的state
看懂了Dva和Vuex的区别和相似点,我们就能更清晰的理解Dva在antd项目中的作用和基本的应用逻辑. 我们只需要比葫芦画瓢即可实现Dva的状态管理即可.
Dva与model
model是antd状态管理文件,是Dva的实现.. 项目采用了umi创建, Dva以umi插件的形式集成到项目中,不用我们手动引入,插件名称:@umijs/plugin-dva .
注意点:
- 内置 dva,默认版本是
^2.6.0-beta.20
,如果项目中有依赖,会优先使用项目中依赖的版本。 - 约定式的 model 组织方式,不用手动注册 model
- 文件名即 namespace,model 内如果没有声明 namespace,会以文件名作为 namespace
- 内置 dva-loading,直接 connect
loading
字段使用即可 - 支持 immer,通过配置
immer
开启
约定式的 model 组织方式:
符合以下规则的文件会被认为是 model 文件,
src/models
下的文件src/pages
下,子目录中 models 目录下的文件src/pages
下,所有 model.ts 文件登录与dva集成
1. 接口封装
需求: 用户发送登录请求,返回用户基本信息,保存用户基本信息,并跳转后台欢迎页.
antd项目中所有的接口都封装在services下的文件, 这个和vue-element-admin中我们的封装方式基本相同. 这里采用了fetch进行网络请求.
配置代理并重启: 代理文件// # https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
config/proxy.ts
. ```typescript // host为: http://leju.bufan.cloud // 接口的相对路径以 /lejuAdmin 开头 export default { dev: { // 新增代理 // 注意不能写^ ‘/lejuAdmin/‘: {
}, ‘/api/‘: {target: 'http://leju.bufan.cloud',
changeOrigin: true,
pathRewrite: { '^': '' },
}, }, test: { ‘/api/‘: {target: 'https://preview.pro.ant.design',
changeOrigin: true,
pathRewrite: { '^': '' },
}, }, pre: { ‘/api/‘: {target: 'https://preview.pro.ant.design',
changeOrigin: true,
pathRewrite: { '^': '' },
}, }, };target: 'your pre url',
changeOrigin: true,
pathRewrite: { '^': '' },
找到文件 `services/login.ts` , 添加方法`doLogin`:<br />**注意: **可能有同学的编译器(vsCode)中会提示ts类型错误, 这个暂时不用处理. ts是完全兼容js的,先按照js的语法实现,随后我们会找机会把js语法改为ts.
```typescript
import request from '@/utils/request';
export type LoginParamsType = {
userName: string;
password: string;
mobile: string;
captcha: string;
};
export async function fakeAccountLogin(params: LoginParamsType) {
return request('/api/login/account', {
method: 'POST',
data: params,
});
}
export async function getFakeCaptcha(mobile: string) {
return request(`/api/login/captcha?mobile=${mobile}`);
}
/**
* 登录接口
* @param mobile
*/
export async function doLogin(params) {
return request(`/lejuAdmin/index/login`,{
method: 'POST',
data: params
});
}
2. 创建Model
创建 pages/Login/model.ts
来管理当前model(文档: 如何识别为model文件?)
请求从组件页面通过 dispatch 调用到 Model(Dva实现)的 Effect(异步), 在Effect中发送网络请求, 得到结果后调用 Reducer 存入Model的state, 页面需要通过 Dva的 connect连接, 进而state可以在页面中展示.
这里我们只是创建了model,但参数暂时空缺,我们需要观察 action 是什么.
语法参考: 文档
import {doLogin as loginApi} from '@/services/login';
const M = {
namespace: 'login',
state: {
userInfo: {}
},
effects: {
/*
调用api,因为是异步,这里采用js generator语法把回调扁平化.类似于async+await
action为载荷对象(同vuex), 第二个参数解构出来的call和put是固定写法:
call用于调用接口(Promise类型)
put用于调用reducer设置state
*/
*doLogin(action, {call,put}){
// 打印action,查看对象结构
console.log(action);
const rs = yield call(loginApi);
put({
type: 'initUserInfo',
payload: rs
})
}
},
reducers: {
initUserInfo({data}){
return {
}
}
}
}
export default M;
3. 与登录组件关联
从上面图例可以看出来,mode想要使用,必须用connect将model和page进行连接. 并且我们在页面中需要调用Effect doLogin
,就需要用到dispatch方法. 修改 pages/Login/index.tsx
代码:
import React from 'react';
import { Form, Input, Button } from 'antd';
import styles from './index.less';
// 引入connect 连接model
import {connect} from 'umi';
// 定义明明空间 方便引用
const namespace = 'myLogin';
const Login = (props)=>{
// 将我们需要的state解构出来
// dispatch是connect之后才有的属性,用于发送请求调用model的effect
const {userInfo,dispatch} = props;
// 提交表单且数据验证成功后回调事件
const onFinish = v=>{
// 这里可以获取到表单内容,则直接调用 model的doLogin
dispatch({
type: `${namespace}/doLogin`,
payload: v
})
console.log('v',v);
}
// 提交表单且数据验证失败后回调事件
const onFinishFailed = v=>{
console.log('ev',v);
}
return (
<Form
className={styles.main}
labelCol = {{span: 8}}
wrapperCol = {{span: 16}}
onFinish= {onFinish}
onFinishFailed = {onFinishFailed}
>
<Form.Item
label="用户名"
name="username"
rules={[
{required: true ,message: '用户名不能为空!'}
]}
>
<Input/>
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[
{required: true, message: '密码不能为空!'}
]}
>
<Input.Password/>
</Form.Item>
<Form.Item
wrapperCol={
{
offset: 8,
span: 16
}
}
>
<Button type="primary" htmlType="submit">登录</Button>
</Form.Item>
</Form>
)
}
// # https://www.redux.org.cn/docs/react-redux/api.html
// 由于命名空间的划分,我们只需要获取 login 的model
const mapStateToProps = (state)=>{
return{
userInfo: state[namespace].userInfo
}
}
// 通过connect 连接 model与react组件
export default connect(mapStateToProps)(Login);
我们看到action有两个参数, type
和payload
, type是发送的aciton名称,包含命名空间, payload是 载荷对象 ,名字是约定的,都叫payload.
所以我们修改 pages/Login/index.tsx
, 给登录添加参数(username+password) ,同时把返回的结果存入state.
import {doLogin as loginApi} from '@/services/login';
import {message} from 'antd';
const M = {
namespace: 'myLogin',
state: {
userInfo: {}
},
effects: {
/*
调用api,因为是异步,这里采用js generator语法把回调扁平化.类似于async+await
action为载荷对象(同vuex), 第二个参数解构出来的call和put是固定写法:
call用于调用接口(Promise类型)
put用于调用reducer设置state
*/
*doLogin(action, {call,put}){
// 打印action,查看对象结构
// console.log(action);
const {payload} = action;
// message与引入模块冲突 重命名为: errMsg
const {data,success,message:errMsg} = yield call(loginApi,payload);
if(success){
message.success('登录成功!');
// 存入state
// put专门用于调用 reducer, 而且需要添加 迭代器 yield修饰符
yield put({
type: 'initUserInfo',
payload: data
})
}else{
// 返回错误 提示错误信息
message.error(errMsg);
}
}
},
reducers: {
// 第二个参数为action: {type:string ,payload: any}
// 把payload解构出来 并获取userInfo
initUserInfo(state,{payload:{userInfo}}){
// 由于单向数据流的设计理念 reducer每次需要重新返回新的完整的state
return {
...state,
userInfo
}
}
}
}
export default M;
4. 使用state
至此,我们已经成功通过 model 实现了登录接口的调用,并获取到了返回值. 我们把userInfo存入model的state,即可以在page中通过connet连接后,通过props访问.
const Login = (props)=>{
// 将我们需要的state解构出来
// dispatch是connect之后才有的属性,用于发送请求调用model的effect
// userInfo可以通过 mapStateToPros connect到当前组件
const {userInfo,dispatch} = props;
...
<Button type="primary" htmlType="submit">登录</Button>
</Form.Item>
{/* 随意添加一个测试div 用于显示userInfo */}
<div>
{JSON.stringify(userInfo)}
</div>
</Form>
5. 总结
我们通过登录接口,给大家演示了and中如果请求接口的逻辑分层.我们知道了Dva状态管理器的解构,与react组件的基本交互. 举一反三,如何在react组件中修改Model中的state,应该也是清楚了: 通过dispatch一个action,调用reducer(同步),或者effect(异步) ,来达到修改state的目的,这个和Vuex思路基本是一致的,也不用多讲.
接下来我们将继续完善登录的认证功能及其他相关功能.