基础使用分析
1、”@” 具体代表什么含义?
还有就是我们会经常看到这样的引入方式:
import GlobalFooter from '@/components/GlobalFooter';
是不是很熟悉,其中 “@” 代表什么意思呢?
其实,这是 Ant Design Pro 为了方便引入组件,就给常用模块设置的别名,这里的 “@”,代表 “/scr/“ 文件夹。
上面 “./Profile/BasicProfile” 代表在 “/src/pages/“ 目录下。
如果想知道别名的详细配置:webpack别名配置
2、UI页面组成部分有哪些?

3、@connect()是什么呢
connect 与 @connect 是react dva的代码装饰器。
connect的作用是将组件和models结合在一起。将models中的state绑定到组件的props中。并提供一些额外的功能,譬如dispatch
connect 的使用
connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。
connect 方法传入的第一个参数是 mapStateToProps 函数,该函数需要返回一个对象,用于建立 State 到 Props 的映射关系。
简而言之,connect接收一个函数,返回一个函数。
第一个函数会注入全部的models,你需要返回一个新的对象,挑选该组件所需要的models。
export default connect(({ user, login, global = {}, loading }) => ({currentUser: user.currentUser,collapsed: global.collapsed,fetchingNotices: loading.effects['global/fetchNotices'],notices: global.notices}))(BasicLayout);// 简化版export default connect(({ user, login, global = {}, loading }) => { return {currentUser: user.currentUser,collapsed: global.collapsed,fetchingNotices: loading.effects['global/fetchNotices'],notices: global.notices}})(BasicLayout);
@connect的使用
其实只是connect的装饰器、语法糖罢了。
将 model 和 component 串联起来
export default connect(({ user, login, global = {}, loading }) => ({currentUser: user.currentUser,collapsed: global.collapsed,fetchingNotices: loading.effects['global/fetchNotices'],notices: global.notices,menuData: login.menuData, // by hzyredirectData: login.redirectData, // by hzy}))(BasicLayout);
改为这样(export 的不再是connect,而是class组件本身。),也是可以执行的,但要注意@connect必须放在export default class前面:
// 将 model 和 component 串联起来@connect(({ user, login, global = {}, loading }) => ({currentUser: user.currentUser,collapsed: global.collapsed,fetchingNotices: loading.effects['global/fetchNotices'],notices: global.notices,menuData: login.menuData,redirectData: login.redirectData,}))export default class BasicLayout extends React.PureComponent { // ...}
Dva 数据处理与搬运分析
1、分析models源码
import { stringify } from 'querystring';import type { Reducer, Effect } from 'umi';import { history } from 'umi';import { fakeAccountLogin } from '@/services/auth/login';// import { queryCurrent } from '@/services/auth/user';// import { setAuthority } from '@/utils/authority';import { getPageQuery } from '@/utils/utils';import { message } from 'antd';export type StateType = {status?: 'ok' | 'error';type?: string;currentAuthority?: 'user' | 'guest' | 'admin';access_token?:string;refresh_token?:string;expireTime?:number;};export type LoginModelType = {namespace: string;state: StateType;effects: {login: Effect;logout: Effect;};reducers: {changeLoginStatus: Reducer<StateType>;};};const Model: LoginModelType = {namespace: 'login',state: {status: undefined,},effects: {*login({ payload }, { call, put }) {const response = yield call(fakeAccountLogin, payload);yield put({type: 'changeLoginStatus',payload: response,});// //查询权限console.log('-----------------登录成功-------------')// Login successfullyconst urlParams = new URL(window.location.href);const params = getPageQuery();message.success('🎉 🎉 🎉 登录成功!');let { redirect } = params as { redirect: string };if (redirect) {const redirectUrlParams = new URL(redirect);if (redirectUrlParams.origin === urlParams.origin) {redirect = redirect.substr(urlParams.origin.length);if (window.routerBase !== '/') {redirect = redirect.replace(window.routerBase, '/');}if (redirect.match(/^\/.*#/)) {redirect = redirect.substr(redirect.indexOf('#') + 1);}} else {window.location.href = '/';return;}history.replace(redirect || '/');}},logout() {const { redirect } = getPageQuery();// Note: There may be security issues, please noteif (window.location.pathname !== '/user/login' && !redirect) {history.replace({pathname: '/user/login',search: stringify({redirect: window.location.href,}),});}//清空tokenlocalStorage.setItem("access_token","");localStorage.setItem("refresh_token","");localStorage.setItem("expireTime","");},},reducers: {changeLoginStatus(state, { payload }) {localStorage.setItem("access_token",data.access_token);localStorage.setItem("refresh_token",data.refresh_token);const current = new Date()const expireTime = current.setTime(current.getTime() + 1000 * data.expires_in)localStorage.setItem("expireTime",expireTime+"");// setAuthority(payload.currentAuthority);return {...state,status: payload.status,type: payload.type,access_token:data.access_token,refresh_token:data.refresh_token,expireTime:expireTime,};},},};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:
处理所有的同步逻辑,将数据返回给页面。
既然知道它们的含义,它们如何有何关系?

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

这张图表是通过 访问 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 函数,用来进行异步请求;
-
调用阶段
拿到dispatch
const { dispatch } = this.propsdispatch({type: 'login/login',payload: { ...values, type ,key:randomId},});
可以直接调用 effects, 也可以直接调用reducers。如果是同名的话,会一起调用。优先执行reducers。
【dispatch 方法从哪里来?被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。】 ``` effects: { *login({ payload }, { call, put }) { const response = yield call(fakeAccountLogin, payload); yield put({type: 'changeLoginStatus',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) {
const redirectUrlParams = new URL(redirect);if (redirectUrlParams.origin === urlParams.origin) {redirect = redirect.substr(urlParams.origin.length);if (window.routerBase !== '/') {redirect = redirect.replace(window.routerBase, '/');}if (redirect.match(/^\/.*#/)) {redirect = redirect.substr(redirect.indexOf('#') + 1);}} else {window.location.href = '/';return;}history.replace(redirect || '/');
} },
logout() { const { redirect } = getPageQuery(); // Note: There may be security issues, please note if (window.location.pathname !== ‘/user/login’ && !redirect) {
history.replace({pathname: '/user/login',search: stringify({redirect: window.location.href,}),});
} //清空token localStorage.setItem(“access_token”,””); localStorage.setItem(“refresh_token”,””); localStorage.setItem(“expireTime”,””); }, },
我们发现每个自定义函数前都有 " * " 修饰 ,函数有两个参数。<br />有小伙伴反映说,对( { payload } , { call, put })函数参数不太理解,这是固定写法吗?里面还有其他关键字吗?<br />那好,我们就一起在控制台上打印出这两个参数:(action,effects)<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);对登录发起请求,登录成功后获取登录返回信息。
将请求完成的舒服
