基础使用分析

1、”@” 具体代表什么含义?

还有就是我们会经常看到这样的引入方式:

  1. import GlobalFooter from '@/components/GlobalFooter';

是不是很熟悉,其中 “@” 代表什么意思呢?

其实,这是 Ant Design Pro 为了方便引入组件,就给常用模块设置的别名,这里的 “@”,代表 “/scr/“ 文件夹。
上面 “./Profile/BasicProfile” 代表在 “/src/pages/“ 目录下。

如果想知道别名的详细配置:webpack别名配置

2、UI页面组成部分有哪些?

image.png

3、@connect()是什么呢

connect 与 @connect 是react dva的代码装饰器。
connect的作用是将组件和models结合在一起。将models中的state绑定到组件的props中。并提供一些额外的功能,譬如dispatch

connect 的使用

connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。
connect 方法传入的第一个参数是 mapStateToProps 函数,该函数需要返回一个对象,用于建立 State 到 Props 的映射关系。
简而言之,connect接收一个函数,返回一个函数。
第一个函数会注入全部的models,你需要返回一个新的对象,挑选该组件所需要的models。

  1. export default connect(({ user, login, global = {}, loading }) => ({
  2. currentUser: user.currentUser,
  3. collapsed: global.collapsed,
  4. fetchingNotices: loading.effects['global/fetchNotices'],
  5. notices: global.notices
  6. }))(BasicLayout);
  7. // 简化版
  8. export default connect(
  9. ({ user, login, global = {}, loading }) => { return {
  10. currentUser: user.currentUser,
  11. collapsed: global.collapsed,
  12. fetchingNotices: loading.effects['global/fetchNotices'],
  13. notices: global.notices
  14. }
  15. }
  16. )(BasicLayout);

@connect的使用

其实只是connect的装饰器、语法糖罢了。
将 model 和 component 串联起来

  1. export default connect(({ user, login, global = {}, loading }) => ({
  2. currentUser: user.currentUser,
  3. collapsed: global.collapsed,
  4. fetchingNotices: loading.effects['global/fetchNotices'],
  5. notices: global.notices,
  6. menuData: login.menuData, // by hzy
  7. redirectData: login.redirectData, // by hzy
  8. }))(BasicLayout);

改为这样(export 的不再是connect,而是class组件本身。),也是可以执行的,但要注意@connect必须放在export default class前面:

  1. // 将 model 和 component 串联起来
  2. @connect(({ user, login, global = {}, loading }) => ({
  3. currentUser: user.currentUser,
  4. collapsed: global.collapsed,
  5. fetchingNotices: loading.effects['global/fetchNotices'],
  6. notices: global.notices,
  7. menuData: login.menuData,
  8. redirectData: login.redirectData,
  9. }))
  10. export default class BasicLayout extends React.PureComponent { // ...
  11. }

Dva 数据处理与搬运分析

1、分析models源码

  1. import { stringify } from 'querystring';
  2. import type { Reducer, Effect } from 'umi';
  3. import { history } from 'umi';
  4. import { fakeAccountLogin } from '@/services/auth/login';
  5. // import { queryCurrent } from '@/services/auth/user';
  6. // import { setAuthority } from '@/utils/authority';
  7. import { getPageQuery } from '@/utils/utils';
  8. import { message } from 'antd';
  9. export type StateType = {
  10. status?: 'ok' | 'error';
  11. type?: string;
  12. currentAuthority?: 'user' | 'guest' | 'admin';
  13. access_token?:string;
  14. refresh_token?:string;
  15. expireTime?:number;
  16. };
  17. export type LoginModelType = {
  18. namespace: string;
  19. state: StateType;
  20. effects: {
  21. login: Effect;
  22. logout: Effect;
  23. };
  24. reducers: {
  25. changeLoginStatus: Reducer<StateType>;
  26. };
  27. };
  28. const Model: LoginModelType = {
  29. namespace: 'login',
  30. state: {
  31. status: undefined,
  32. },
  33. effects: {
  34. *login({ payload }, { call, put }) {
  35. const response = yield call(fakeAccountLogin, payload);
  36. yield put({
  37. type: 'changeLoginStatus',
  38. payload: response,
  39. });
  40. // //查询权限
  41. console.log('-----------------登录成功-------------')
  42. // Login successfully
  43. const urlParams = new URL(window.location.href);
  44. const params = getPageQuery();
  45. message.success('🎉 🎉 🎉 登录成功!');
  46. let { redirect } = params as { redirect: string };
  47. if (redirect) {
  48. const redirectUrlParams = new URL(redirect);
  49. if (redirectUrlParams.origin === urlParams.origin) {
  50. redirect = redirect.substr(urlParams.origin.length);
  51. if (window.routerBase !== '/') {
  52. redirect = redirect.replace(window.routerBase, '/');
  53. }
  54. if (redirect.match(/^\/.*#/)) {
  55. redirect = redirect.substr(redirect.indexOf('#') + 1);
  56. }
  57. } else {
  58. window.location.href = '/';
  59. return;
  60. }
  61. history.replace(redirect || '/');
  62. }
  63. },
  64. logout() {
  65. const { redirect } = getPageQuery();
  66. // Note: There may be security issues, please note
  67. if (window.location.pathname !== '/user/login' && !redirect) {
  68. history.replace({
  69. pathname: '/user/login',
  70. search: stringify({
  71. redirect: window.location.href,
  72. }),
  73. });
  74. }
  75. //清空token
  76. localStorage.setItem("access_token","");
  77. localStorage.setItem("refresh_token","");
  78. localStorage.setItem("expireTime","");
  79. },
  80. },
  81. reducers: {
  82. changeLoginStatus(state, { payload }) {
  83. localStorage.setItem("access_token",data.access_token);
  84. localStorage.setItem("refresh_token",data.refresh_token);
  85. const current = new Date()
  86. const expireTime = current.setTime(current.getTime() + 1000 * data.expires_in)
  87. localStorage.setItem("expireTime",expireTime+"");
  88. // setAuthority(payload.currentAuthority);
  89. return {
  90. ...state,
  91. status: payload.status,
  92. type: payload.type,
  93. access_token:data.access_token,
  94. refresh_token:data.refresh_token,
  95. expireTime:expireTime,
  96. };
  97. },
  98. },
  99. };
  100. export default Model;

对于不熟悉dva或者redux的小伙伴来讲,肯定看到的一头雾水。不过,不用怕,我们一起分析它。
Dav官网地址:https://dvajs.com/
其实我们分析观察到dva中的每个model,实际上都是普通的JavaScript对象,包含

  • namespace
  • state
  • reducers
  • effects

namespace
该字段就相当于model的索引,根据该命名空间就可以找到页面对应的model。注意 namespace 必须唯一。

state:
state 是储存数据的地方,收到 Action 以后,会更新数据。

effects
处理所有的异步逻辑,将返回结果以Action的形式交给reducer处理。

reducers
处理所有的同步逻辑,将数据返回给页面。

既然知道它们的含义,它们如何有何关系?

Ant Design Pro 工作流程 - 图2

这张图表是不参与服务器传递数据的,通过View页面中的点击事件或者其他触发 dispatch 的Action 改变 State 的数据。所以,随着 state 发生改变,页面也会重新渲染。

Ant Design Pro 工作流程 - 图3

这张图表是通过 访问 URL 触发 effect 的异步从服务器请求数据,将拿到的数据 data ,再通过 reducer 同步到 state 中,即 state 值发生变化,页面也会随之改变。

2、分析数据请求过程

dva的几个规则

1、通过dispatch调用namespace/effects
2、state(状态)
3、effects (异步操作)
- 函数必须带*,也就是生成器。
- 第一个参数,可以拓展为{payload, callback}
- 第二个参数,call和put
- call 就是调用 async的action函数
- put就是调用reducers的函数来更新state。
4、reducers
5、dva是以model为单位的,所有的应用逻辑都在上面

准备阶段

  • 定义 state 状态,用以绑定到 view 层;
  • 定义 effects
  • call用来调用 action,类似dispatch
  • put用来调用reducers
  • 定义 sync action 函数,用来进行异步请求;
  • 定义 reducers 函数,用来更新 state。

    调用阶段

    拿到dispatch

    1. const { dispatch } = this.props
    2. dispatch({
    3. type: 'login/login',
    4. payload: { ...values, type ,key:randomId},
    5. });

    可以直接调用 effects, 也可以直接调用reducers。如果是同名的话,会一起调用。优先执行reducers。
    【dispatch 方法从哪里来?被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。】 ``` effects: { *login({ payload }, { call, put }) { const response = yield call(fakeAccountLogin, payload); yield put({

    1. type: 'changeLoginStatus',
    2. payload: response,

    }); // //查询权限 console.log(‘————————-登录成功——————-‘) // Login successfully console.log(response) const urlParams = new URL(window.location.href); const params = getPageQuery(); message.success(‘🎉 🎉 🎉 登录成功!’); let { redirect } = params as { redirect: string }; if (redirect) {

    1. const redirectUrlParams = new URL(redirect);
    2. if (redirectUrlParams.origin === urlParams.origin) {
    3. redirect = redirect.substr(urlParams.origin.length);
    4. if (window.routerBase !== '/') {
    5. redirect = redirect.replace(window.routerBase, '/');
    6. }
    7. if (redirect.match(/^\/.*#/)) {
    8. redirect = redirect.substr(redirect.indexOf('#') + 1);
    9. }
    10. } else {
    11. window.location.href = '/';
    12. return;
    13. }
    14. history.replace(redirect || '/');

    } },

    logout() { const { redirect } = getPageQuery(); // Note: There may be security issues, please note if (window.location.pathname !== ‘/user/login’ && !redirect) {

    1. history.replace({
    2. pathname: '/user/login',
    3. search: stringify({
    4. redirect: window.location.href,
    5. }),
    6. });

    } //清空token localStorage.setItem(“access_token”,””); localStorage.setItem(“refresh_token”,””); localStorage.setItem(“expireTime”,””); }, },

  1. 我们发现每个自定义函数前都有 " * " 修饰 ,函数有两个参数。<br />有小伙伴反映说,对( { payload } , { call, put })函数参数不太理解,这是固定写法吗?里面还有其他关键字吗?<br />那好,我们就一起在控制台上打印出这两个参数:(actioneffects)<br />我们根据引入路径 "import { fakeAccountLogin } from '@/services/auth/login';" 找到 api.js ,如下

export async function fakeAccountLogin(params: LoginParamsType) { params = {…params , grant_type : ‘password’} return request(‘/auth/oauth/token?’+tansParams(params), { method: ‘POST’, data: params, headers: { Authorization: settings.authorizationValue+””, }, }); } ``` 此处 yield call(fakeAccountLogin, payload);对登录发起请求,登录成功后获取登录返回信息。

将请求完成的舒服