接口说明

项目接口

项目接口: http://leju.bufan.cloud/swagger-ui.html
服务器host: http://leju.bufan.cloud

接口说明,当前接口为不凡学院后期项目”乐居商城”项目接口. 在中后台项目开发中,力争采用真实项目接口,模拟真实开发情景,以达到学以致用的目的. 在这里可以采用该接口进行测试.

image.png

登录接口

登录接口: /lejuAdmin/index/login , 请求类型: post.
image.png

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**的需求. 所以这是一种三层结构.
image.png

Dva简介

什么是dva

dva 首先是一个基于 reduxredux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-routerfetch,所以也可以理解为一个轻量级的应用框架。
image.png

Dva与vuex的区别

Dva的状态管理有点类似于vuex,但有区别:
image.png
区别:

  • 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进行网络请求.
    1. // # https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
    2. fetch('http://example.com/movies.json')
    3. .then(function(response) {
    4. return response.json();
    5. })
    6. .then(function(myJson) {
    7. console.log(myJson);
    8. });
    配置代理并重启: 代理文件 config/proxy.ts . ```typescript // host为: http://leju.bufan.cloud // 接口的相对路径以 /lejuAdmin 开头 export default { dev: { // 新增代理 // 注意不能写^ ‘/lejuAdmin/‘: {
    1. target: 'http://leju.bufan.cloud',
    2. changeOrigin: true,
    3. pathRewrite: { '^': '' },
    }, ‘/api/‘: {
    1. target: 'https://preview.pro.ant.design',
    2. changeOrigin: true,
    3. pathRewrite: { '^': '' },
    }, }, test: { ‘/api/‘: {
    1. target: 'https://preview.pro.ant.design',
    2. changeOrigin: true,
    3. pathRewrite: { '^': '' },
    }, }, pre: { ‘/api/‘: {
    1. target: 'your pre url',
    2. changeOrigin: true,
    3. pathRewrite: { '^': '' },
    }, }, };
  1. 找到文件 `services/login.ts` , 添加方法`doLogin`:<br />**注意: **可能有同学的编译器(vsCode)中会提示ts类型错误, 这个暂时不用处理. ts是完全兼容js的,先按照js的语法实现,随后我们会找机会把js语法改为ts.
  2. ```typescript
  3. import request from '@/utils/request';
  4. export type LoginParamsType = {
  5. userName: string;
  6. password: string;
  7. mobile: string;
  8. captcha: string;
  9. };
  10. export async function fakeAccountLogin(params: LoginParamsType) {
  11. return request('/api/login/account', {
  12. method: 'POST',
  13. data: params,
  14. });
  15. }
  16. export async function getFakeCaptcha(mobile: string) {
  17. return request(`/api/login/captcha?mobile=${mobile}`);
  18. }
  19. /**
  20. * 登录接口
  21. * @param mobile
  22. */
  23. export async function doLogin(params) {
  24. return request(`/lejuAdmin/index/login`,{
  25. method: 'POST',
  26. data: params
  27. });
  28. }

2. 创建Model

创建 pages/Login/model.ts 来管理当前model(文档: 如何识别为model文件?)
请求从组件页面通过 dispatch 调用到 Model(Dva实现)的 Effect(异步), 在Effect中发送网络请求, 得到结果后调用 Reducer 存入Model的state, 页面需要通过 Dva的 connect连接, 进而state可以在页面中展示.

PPrerEAKbIoDZYr.png
这里我们只是创建了model,但参数暂时空缺,我们需要观察 action 是什么.
语法参考: 文档

  1. import {doLogin as loginApi} from '@/services/login';
  2. const M = {
  3. namespace: 'login',
  4. state: {
  5. userInfo: {}
  6. },
  7. effects: {
  8. /*
  9. 调用api,因为是异步,这里采用js generator语法把回调扁平化.类似于async+await
  10. action为载荷对象(同vuex), 第二个参数解构出来的call和put是固定写法:
  11. call用于调用接口(Promise类型)
  12. put用于调用reducer设置state
  13. */
  14. *doLogin(action, {call,put}){
  15. // 打印action,查看对象结构
  16. console.log(action);
  17. const rs = yield call(loginApi);
  18. put({
  19. type: 'initUserInfo',
  20. payload: rs
  21. })
  22. }
  23. },
  24. reducers: {
  25. initUserInfo({data}){
  26. return {
  27. }
  28. }
  29. }
  30. }
  31. export default M;

3. 与登录组件关联

从上面图例可以看出来,mode想要使用,必须用connect将model和page进行连接. 并且我们在页面中需要调用Effect doLogin,就需要用到dispatch方法. 修改 pages/Login/index.tsx代码:

  1. import React from 'react';
  2. import { Form, Input, Button } from 'antd';
  3. import styles from './index.less';
  4. // 引入connect 连接model
  5. import {connect} from 'umi';
  6. // 定义明明空间 方便引用
  7. const namespace = 'myLogin';
  8. const Login = (props)=>{
  9. // 将我们需要的state解构出来
  10. // dispatch是connect之后才有的属性,用于发送请求调用model的effect
  11. const {userInfo,dispatch} = props;
  12. // 提交表单且数据验证成功后回调事件
  13. const onFinish = v=>{
  14. // 这里可以获取到表单内容,则直接调用 model的doLogin
  15. dispatch({
  16. type: `${namespace}/doLogin`,
  17. payload: v
  18. })
  19. console.log('v',v);
  20. }
  21. // 提交表单且数据验证失败后回调事件
  22. const onFinishFailed = v=>{
  23. console.log('ev',v);
  24. }
  25. return (
  26. <Form
  27. className={styles.main}
  28. labelCol = {{span: 8}}
  29. wrapperCol = {{span: 16}}
  30. onFinish= {onFinish}
  31. onFinishFailed = {onFinishFailed}
  32. >
  33. <Form.Item
  34. label="用户名"
  35. name="username"
  36. rules={[
  37. {required: true ,message: '用户名不能为空!'}
  38. ]}
  39. >
  40. <Input/>
  41. </Form.Item>
  42. <Form.Item
  43. label="密码"
  44. name="password"
  45. rules={[
  46. {required: true, message: '密码不能为空!'}
  47. ]}
  48. >
  49. <Input.Password/>
  50. </Form.Item>
  51. <Form.Item
  52. wrapperCol={
  53. {
  54. offset: 8,
  55. span: 16
  56. }
  57. }
  58. >
  59. <Button type="primary" htmlType="submit">登录</Button>
  60. </Form.Item>
  61. </Form>
  62. )
  63. }
  64. // # https://www.redux.org.cn/docs/react-redux/api.html
  65. // 由于命名空间的划分,我们只需要获取 login 的model
  66. const mapStateToProps = (state)=>{
  67. return{
  68. userInfo: state[namespace].userInfo
  69. }
  70. }
  71. // 通过connect 连接 model与react组件
  72. export default connect(mapStateToProps)(Login);

image.png
我们看到action有两个参数, typepayload , type是发送的aciton名称,包含命名空间, payload是 载荷对象 ,名字是约定的,都叫payload.
所以我们修改 pages/Login/index.tsx , 给登录添加参数(username+password) ,同时把返回的结果存入state.

  1. import {doLogin as loginApi} from '@/services/login';
  2. import {message} from 'antd';
  3. const M = {
  4. namespace: 'myLogin',
  5. state: {
  6. userInfo: {}
  7. },
  8. effects: {
  9. /*
  10. 调用api,因为是异步,这里采用js generator语法把回调扁平化.类似于async+await
  11. action为载荷对象(同vuex), 第二个参数解构出来的call和put是固定写法:
  12. call用于调用接口(Promise类型)
  13. put用于调用reducer设置state
  14. */
  15. *doLogin(action, {call,put}){
  16. // 打印action,查看对象结构
  17. // console.log(action);
  18. const {payload} = action;
  19. // message与引入模块冲突 重命名为: errMsg
  20. const {data,success,message:errMsg} = yield call(loginApi,payload);
  21. if(success){
  22. message.success('登录成功!');
  23. // 存入state
  24. // put专门用于调用 reducer, 而且需要添加 迭代器 yield修饰符
  25. yield put({
  26. type: 'initUserInfo',
  27. payload: data
  28. })
  29. }else{
  30. // 返回错误 提示错误信息
  31. message.error(errMsg);
  32. }
  33. }
  34. },
  35. reducers: {
  36. // 第二个参数为action: {type:string ,payload: any}
  37. // 把payload解构出来 并获取userInfo
  38. initUserInfo(state,{payload:{userInfo}}){
  39. // 由于单向数据流的设计理念 reducer每次需要重新返回新的完整的state
  40. return {
  41. ...state,
  42. userInfo
  43. }
  44. }
  45. }
  46. }
  47. export default M;

image.png

4. 使用state

至此,我们已经成功通过 model 实现了登录接口的调用,并获取到了返回值. 我们把userInfo存入model的state,即可以在page中通过connet连接后,通过props访问.

  1. const Login = (props)=>{
  2. // 将我们需要的state解构出来
  3. // dispatch是connect之后才有的属性,用于发送请求调用model的effect
  4. // userInfo可以通过 mapStateToPros connect到当前组件
  5. const {userInfo,dispatch} = props;
  6. ...
  7. <Button type="primary" htmlType="submit">登录</Button>
  8. </Form.Item>
  9. {/* 随意添加一个测试div 用于显示userInfo */}
  10. <div>
  11. {JSON.stringify(userInfo)}
  12. </div>
  13. </Form>

image.png

5. 总结

我们通过登录接口,给大家演示了and中如果请求接口的逻辑分层.我们知道了Dva状态管理器的解构,与react组件的基本交互. 举一反三,如何在react组件中修改Model中的state,应该也是清楚了: 通过dispatch一个action,调用reducer(同步),或者effect(异步) ,来达到修改state的目的,这个和Vuex思路基本是一致的,也不用多讲.
接下来我们将继续完善登录的认证功能及其他相关功能.