基础使用分析
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 hzy
redirectData: 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 successfully
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","");
},
},
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.props
dispatch({
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);对登录发起请求,登录成功后获取登录返回信息。
将请求完成的舒服