接口说明
项目接口
项目接口: 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_Fetchfetch('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.```typescriptimport 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+awaitaction为载荷对象(同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 连接modelimport {connect} from 'umi';// 定义明明空间 方便引用const namespace = 'myLogin';const Login = (props)=>{// 将我们需要的state解构出来// dispatch是connect之后才有的属性,用于发送请求调用model的effectconst {userInfo,dispatch} = props;// 提交表单且数据验证成功后回调事件const onFinish = v=>{// 这里可以获取到表单内容,则直接调用 model的doLogindispatch({type: `${namespace}/doLogin`,payload: v})console.log('v',v);}// 提交表单且数据验证失败后回调事件const onFinishFailed = v=>{console.log('ev',v);}return (<FormclassName={styles.main}labelCol = {{span: 8}}wrapperCol = {{span: 16}}onFinish= {onFinish}onFinishFailed = {onFinishFailed}><Form.Itemlabel="用户名"name="username"rules={[{required: true ,message: '用户名不能为空!'}]}><Input/></Form.Item><Form.Itemlabel="密码"name="password"rules={[{required: true, message: '密码不能为空!'}]}><Input.Password/></Form.Item><Form.ItemwrapperCol={{offset: 8,span: 16}}><Button type="primary" htmlType="submit">登录</Button></Form.Item></Form>)}// # https://www.redux.org.cn/docs/react-redux/api.html// 由于命名空间的划分,我们只需要获取 login 的modelconst 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+awaitaction为载荷对象(同vuex), 第二个参数解构出来的call和put是固定写法:call用于调用接口(Promise类型)put用于调用reducer设置state*/*doLogin(action, {call,put}){// 打印action,查看对象结构// console.log(action);const {payload} = action;// message与引入模块冲突 重命名为: errMsgconst {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解构出来 并获取userInfoinitUserInfo(state,{payload:{userInfo}}){// 由于单向数据流的设计理念 reducer每次需要重新返回新的完整的statereturn {...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思路基本是一致的,也不用多讲.
接下来我们将继续完善登录的认证功能及其他相关功能.
