Dva 概念
- https://dvajs.com/
- https://github.com/dvajs/dva
- redux docs
- redux docs 中文
- Mostly adequate guide to FP
- JS函数式编程指南
- choo docs
- elm
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
数据流向
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过dispatch
发起一个action,如果是 同步行为会直接通过reducers
改变state
,如果是异步行为(副作用)会先触发effects
,然后流向reducers
最终改变state
,所以在dva中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)。
Models 介绍
State
State 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值);操作的时候每次都要当做不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。
在 dva 中你可以通过 dva 的实例属性
_store
看到顶部的 state 数据。
const app = dva();
console.log(app._store); // 顶部的 state 数据
Action
Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从UI事件、网络回调,还是 WebSocket 等数据源所获取的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。
action 必须带有 type
属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch
import {connect} from 'dva';
function App(props){
const action = {
type: 'loginModel/userInfo', //action动作
payload: {}, //额外参数
}
return (<div onClick={() => props.dispatch(action}></div>)
}
export default connect(mapStateToProps)(App)
// 在 dva 中,通过 connect(mapStateToProps, dispatchActionToProps, ...)(App) 连接组件和Model
// 如果dispatchActionToProps不传值时,connect Model 的组件通过 props 可以访问到 dispatch,
// 可以调用 Model 中的 Reducer 或者 Effects
dispatch 函数
type dispatch = (a: Action) => Action
它是一个用于触发 action 的函数,action是改变 Satate 的唯一途径,但是它只描述一个行为,而 dispatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
Reducer
type Reducer<S, A> = (state: S, action: A) => S
Reducer 函数接受2个参数:旧state对象、派发的action,返回新的state对象。
Reducer 必须是纯函数,所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且每一次的计算都应该使用 immutable data,这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。
Effect
Effect 被称为副作用,常见的就是异步操作。它来自于函数编程的概念,之所以叫副作用是因为它使得我们的函数变的不纯,同样的输入不一定获得同样的输出。
dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。至于为什么我们这么纠结于 纯函数,如果你想了解更多可以阅读Mostly adequate guide to FP,或者它的中文译本JS函数式编程指南。
Subscription
Subscription 是一种从 源 获取数据的方法,它来自于 elm。
Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
import key from 'keymaster';
...
app.model({
namespace: 'count',
subscriptions: {
// 键盘事件监听
keyEvent({dispatch}) {
key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
},
// 路由变化监听
setup({ dispatch, history }){
history.listen(location => {
});
}
}
});
Router
这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 History API 可以监听浏览器url的变化,从而控制路由相关操作。
dva 实例提供了 router 方法来控制路由,使用的是react-router。
import { Router, Route } from 'dva/router';
app.router(({history}) =>
<Router history={history}>
<Route path="/" component={HomePage} />
</Router>
);
Route Components
在组件设计方法中,我们提到过 Container Components,在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。
所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在/routes/目录下,而/components/目录下则是纯组件(Presentational Components)。
dva-cli(dva脚手架)
https://github.com/dvajs/dva-cli
npm install -g dva-cli # 全局安装dva-cli
dva -v # 查看dva版本号
# 创建名字为demo的项目,包含项目初始化目录和文件,
# 并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。
# 创建好后,默认打开 http://localhost:8000
dva new demo
初始化项目(以“基本计数器”为例介绍dva)
- history的最新版为5.0,而connect-react-router使用的history版本为4.7,不兼容
- 不显式安装history或者指定老版本history进行安装。否则自己安装的history是使用history5
npx create-react-app 8.dva
cd 8.dva
cnpm i -S dva redux react-redux redux-saga react-router-dom connected-react-router
reducers
src/index.js
import React from 'react';
import dva from 'dva';
import Counter1 from './components/Counter1.js';
import Counter2 from './components/Counter2.js';
// 创建dva实例
const app = dva();
// 定义全局model(可定义多个)
app.model(require('./models/counter1').default);
app.model(require('./models/counter2').default);
// 定义路由规则
// app.router 接收一个函数组件,回传参数是 {app, history}
app.router(({app, history}) => {
console.log(app, history);
return (
<div>
<Counter1 />
<Counter2 />
</div>
)
});
// 启动
app.start('#root');
src/models
src/models/counter1.js
// dva 通过 model 的概念把一个领域的模型管理起来,类似于redux的仓库。
// 包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
export default {
namespace: 'counter1', //命名空间,表示在全局 state 上的 key
state: { //初始值
number: 5,
},
// reducers 等同于 redux 里的 reducer,接收 action,同步更新 state
reducers: {
add(state, action){
return {...state, number: state.number+1};
}
}
}
src/models/counter2.js
export default {
namespace: 'counter2',
state: {
number: 10,
},
reducers: {
add: (state, action) => ({...state, number: state.number+1}),
}
}
src/components
src/components/Counter1.js
// connect就是react-redux的connect,把model和component串联起来。
import {connect} from 'dva';
function Counter1(ps){
return (
<div>
<div>{ps.number}</div>
<div>
<button onClick={() => ps.dispatch({type: 'counter1/add'})}>同步+</button>
</div>
</div>
)
}
let mapStateToProps = state => state.counter1;
export default connect(mapStateToProps)(Counter1);
src/components/Counter1.js
import {connect} from 'dva';
function Counter2(ps){
return (
<div>
<div>{ps.number}</div>
<div>
<button onClick={() => ps.dispatch({type: 'counter2/add'})}>同步+</button>
</div>
</div>
)
}
export default connect(({counter2}) => counter2)(Counter2);
effects
src/models
src/models/delay.js
// 模拟延迟
export const delay = (ms=1000, val=true) => new Promise((resolve, reject) => {
setTimeout(resolve, ms, val);
});
// 模拟异步返回数据
export const delayMock = (data, ms) => delay(ms, {
result: 1,
data,
})
src/models/counter1.js
import {delayMock} from './delay';
// dva 通过 model 的概念把一个领域的模型管理起来,类似于redux的仓库。
// 包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
const counter1 = {
namespace: 'counter1', //命名空间,表示在全局 state 上的 key
state: { //初始值
number: 5,
},
// 异步(副作用)
effects: {
*asyncAdd(action, {call, put}){
const data = yield call(delayMock, {id: 1, name: 'jack'});
console.log('data=>', data);
// 如果在effects里派发动作,如果是派发给自己的model的话,不需要加namespace前缀
yield put({type: 'add'});
// 如果派发给其它model,需要加前缀
yield put({type: 'counter2/add'});
}
},
// reducers 等同于 redux 里的 reducer,接收 action,同步更新 state
reducers: {
add(state, action){
return {...state, number: state.number+1};
}
}
}
export default counter1;
src/models/counter2.js
import {delayMock} from './delay';
const counter2 = {
namespace: 'counter2',
state: {
number: 10,
},
effects: {
*asyncAdd(action, {call, put}){
yield call(delayMock);
yield put({type: 'add'});
}
},
reducers: {
add: (state, action) => ({...state, number: state.number+1}),
}
}
export default counter2;
src/components
src/components/Counter1.js
import {connect} from 'dva';
function Counter1({dispatch, ...ps}){
return (
<div>
<div>{ps.number}</div>
<div>
<button onClick={() => dispatch({type: 'counter1/add'})}>同步+</button>
+ <button onClick={() => dispatch({type: 'counter1/asyncAdd'})}>异步+</button>
</div>
</div>
)
}
let mapStateToProps = state => state.counter1;
export default connect(mapStateToProps)(Counter1);
src/components/Counter1.js
import {connect} from 'dva';
function Counter2({dispatch, ...ps}){
return (
<div>
<div>{ps.number}</div>
<div>
<button onClick={() => dispatch({type: 'counter2/add'})}>同步+</button>
+ <button onClick={() => dispatch({type: 'counter2/asyncAdd'})}>异步+</button>
</div>
</div>
)
}
export default connect(({counter2}) => counter2)(Counter2);
路由使用 dva/route
默认输出 react-router 接口, react-router-redux 的接口通过属性 routerRedux 输出。
src/index.js
import React from 'react';
import dva from 'dva';
import history from './routes/history';
import RouterConfig from './routes';
// 创建dva实例
const app = dva({
history,
});
// 定义全局model(可定义多个)
app.model(require('./models/counter1').default);
app.model(require('./models/counter2').default);
// 定义路由规则
// app.router 接收一个函数组件,回传参数是 {app, history}
// app.router(require('./routes').default);
app.router(RouterConfig);
// 启动
app.start('#root');
src/routes/history.js
// import { createHashHistory } from 'history';
import { createBrowserHistory } from 'history';
const history = createBrowserHistory({
basename: '/web', // 基链接
forceRefresh: false, //是否强制刷新整个页面
// keyLength: 6, //location.key的长度
// getUserConfirmation: (message,callback) => callback(window.confirm(message)) // 跳转拦截函数
});
export default history;
src/routes/index.js
import React from "react";
import {Router, Route, Switch, Link} from "dva/router";
import Counter1 from '../components/Counter1.js';
import Counter2 from '../components/Counter2.js';
function RouterConfig({history, app}){
console.log(history, app);
return (
<div>
<Router history={history}>
<>
<ul>
<li><Link to="counter1">page counter1</Link></li>
<li><Link to="counter2">page counter2</Link></li>
</ul>
<Switch>
<Route path="/counter1" component={props => <Counter1 {...props} />} />
<Route path="/counter2" component={props => <Counter2 {...props} />} />
</Switch>
</>
</Router>
</div>
)
}
export default RouterConfig;
路径跳转
src/routes/index.js
import React from "react";
+ import {Router, Route, Switch, Link, routerRedux} from "dva/router";
import Counter1 from '../components/Counter1';
import Counter2 from '../components/Counter2';
+ const {ConnectedRouter} = routerRedux;
function RouterConfig({history, app}){
console.log(history, app);
return (
<div>
+ <ConnectedRouter history={history}>
<>
<ul>
<li><Link to="counter1">page counter1</Link></li>
<li><Link to="counter2">page counter2</Link></li>
</ul>
<Switch>
<Route path="/counter1" component={props => <Counter1 {...props} />} />
<Route path="/counter2" component={props => <Counter2 {...props} />} />
</Switch>
</>
+ </ConnectedRouter>
</div>
)
}
export default RouterConfig;
src/models/counter1.js
import {delayMock} from './delay';
+ import {routerRedux} from 'dva/router';
const counter1 = {
namespace: 'counter1',
state: {
number: 5,
},
effects: {
*asyncAdd(action, {call, put}){
yield call(delayMock);
yield put({type: 'add'});
},
+ *goto({payload}, {put}){
+ yield put(routerRedux.push(payload));
+ }
},
reducers: {
add(state, action){
return {...state, number: state.number+1};
}
}
}
export default counter1;
src/components/Counter1.js
import {connect} from 'dva';
import { routerRedux } from 'dva/router';
function Counter1({dispatch, ...ps}){
console.log('Counter1', ps);
return (
<div>
<div>{ps.number}</div>
<div>
<button onClick={() => dispatch({type: 'counter1/add'})}>同步+</button>
<button onClick={() => dispatch({type: 'counter1/asyncAdd'})}>异步+</button>
<button onClick={() => {
dispatch({type: 'counter1/goto', payload: '/counter2'})
}}>跳到/counter2</button>
<button onClick={() => {
dispatch(routerRedux.push('/counter2'))
}}>跳到/counter2</button>
</div>
</div>
)
}
let mapStateToProps = ({routing, counter1}) => ({
number: counter1.number,
})
export default connect(mapStateToProps)(Counter1);
dva/dynamic 按需加载model
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app, //dva 实例,加载 models 时需要
//返回 Promise 数组的函数,Promise 返回 dva model
models: () => [
import('./models/users'),
],
//返回 Promise 的函数,Promise 返回 React Component
component: () => import('./routes/UserPage'),
});
基础使用
src/index.js
import dva from 'dva';
import history from './routes/history';
import RouterConfig from './routes';
const app = dva({
history,
});
app.router(RouterConfig);
app.start('#root');
src/routes/core.js
import dynamic from 'dva/dynamic';
/** 创建动态组件
*
* @param {*} app dva 实例,加载 models 时需要
* @param {*} models 该路由组件加载的model数组
* @param {*} component 动态路由组件 () => import('@/pages/Home')
* @returns
*/
export const dynamicWrapper = (app, models, component) => dynamic({
app,
models: () => models,
component,
});
src/routes/index.js
import React from "react";
import {Route, Switch, Link, routerRedux} from "dva/router";
import {dynamicWrapper} from './core';
const {ConnectedRouter} = routerRedux;
// 创建一级路由
const routesData = (app) => {
const Counter1 = dynamicWrapper(
app,
[import('../models/counter1'), import('../models/counter2')],
() => import('../components/Counter1'),
);
const Counter2 = dynamicWrapper(
app,
[import('../models/counter2')],
() => import('../components/Counter2')
);
return (
<Switch>
<Route path="/counter1" component={Counter1} />
<Route path="/counter2" render={props => (
<Counter2 {...props} app={app} /> {/*传递app过去,用于创建二级路由时使用*/}
)}/>
</Switch>
)
}
function RouterConfig({history, app}){
return (
<div>
<ConnectedRouter history={history}>
<>
<ul>
<li><Link to="/counter1">counter1</Link></li>
<li><Link to="/counter2">counter2</Link></li>
<li><Link to="/counter2/movie">counter2/movie</Link></li>
<li><Link to="/counter2/music">counter2/music</Link></li>
</ul>
{routesData(app)}
</>
</ConnectedRouter>
</div>
)
}
export default RouterConfig;
src/models/movie.js
const movie = {
namespace: 'movie',
state: {
movieName: '海上钢琴师',
},
}
export default movie;
src/components/Counter2/index.js
import {connect} from 'dva';
import {Route, Switch} from "dva/router";
import {dynamicWrapper} from '../routes/core';
// 创建二级路由
const routesData = (app) => {
const Movie = dynamicWrapper(
app,
[import('../models/movie')], //路由/counter2/movie 按需加载了 counter2 movie 2个model
() => import('./Movie'),
);
const Music = dynamicWrapper(
app,
[], //路由/counter2/music 按需加载了 counter2 1个model
() => import('./Music')
);
return (
<Switch>
<Route path="/counter2/movie" component={Movie} />
<Route path="/counter2/music" component={Music}/>
</Switch>
)
}
function Counter2(ps){
return (
<div>
<div>{ps.number}</div>
<div>
<button onClick={() => ps.dispatch({type: 'counter2/add'})}>同步+</button>
<button onClick={() => ps.dispatch({type: 'counter2/asyncAdd'})}>异步+</button>
</div>
<div>
<h2>二级路由</h2>
<div>{routesData(ps.app)}</div>
</div>
</div>
)
}
export default connect(({counter2}) => counter2)(Counter2);
src/components/Counter2/Movie.js
import {connect} from "dva";
function App(ps){
console.log('movie=>', ps);
return <div>counter2/movie</div>
}
export default connect(({counter2, movie}) => ({counter2, movie}))(App);
src/componnets/Counter2/Music.js
function Music(){
return <div>counter2/music</div>
}
export default Music;
路由配置简单封装
本例是不向下面组件传递 app
,初始化的时候是一次性把所有路由创建好。
当然,你也可以把 app
向下传,在用到子路由的地方再创建子路由组。
src/routes/core.js
import {Route, Switch, Redirect} from "dva/router";
import dynamic from 'dva/dynamic';
// 设置页面标题
export function PageTitle({title, children}){
document.title = title;
return children;
}
/** 创建动态组件
*
* @param {*} app dva 实例,加载 models 时需要
* @param {*} models 该路由组件加载的model数组
* @param {*} component 动态路由组件 () => import('@/pages/Home')
* @returns
*/
export const dynamicWrapper = (app, models, component) => dynamic({
app,
models: () => models,
component
});
// 路由映射表
window.dva_router_pathMap = {};
/** 创建 组路由
*
* @param {*} app
* @param {*} routesFn 返回一组路由配置的函数
* @param {*} isAddSwitch 是否添加 Switch 包裹
* @param {*} opt.disabledNotFound 是否禁止添加 404 重定向
*/
export const createRoutes = (app, routesFn, isAddSwitch, opt={}) => {
const RoutesComArr = routesFn(app).map(routeConfig => createRoute(app, (app) => routeConfig));
// 是否禁止添加 404 重定向
if (!opt.disabledNotFound){
RoutesComArr.push(<Redirect key={`/not-found_redirect`} to="/not-found" />);
}
if (!isAddSwitch) return <Switch>{RoutesComArr}</Switch>;
return RoutesComArr;
};
/** 创建单个路由
*
* @param {*} app
* @param {*} routeFn 返回一个路由配置的函数
*/
export const createRoute = (app, routeFn) => {
const routeConfig = routeFn(app);
const {component: Com, path, exact, indexRoute, title, ...extraProps} = routeConfig;
if (path && path !== '/'){
window.dva_router_pathMap[path] = { path, title, ...extraProps };
}
let baseTitle = '标题 - {title}';
let outputTitle = title ? baseTitle.replace(/{.*}/gi, title) : baseTitle.slice(0, -10);
let routeProps = Object.assign(
{
key: path || Math.random(4),
render: props => (
<PageTitle title={outputTitle}>
<Com extraProps={extraProps} {...props} />
</PageTitle>
),
},
path && {path},
exact && {exact},
)
if (indexRoute) {
return [
<Redirect key={path + "_redirect"} exact from={path} to={indexRoute} />,
<Route {...routeProps} />
];
}
return <Route {...routeProps} />;
}
src/routes/index.js
import React from "react";
import {Link, Switch, routerRedux} from "dva/router";
import {createRoutes} from './core';
import * as routers from './export';
// 创建函数-返回路由配置数组
const routesFn = (app) => {
return Object.keys(routers).reduce((arr, item) => arr.concat(routers[item](app)), []);
}
const {ConnectedRouter} = routerRedux;
function RouterConfig({history, app}){
return (
<div>
<ConnectedRouter history={history}>
<>
<ul>
<li><Link to="/counter1">counter1</Link></li>
<li><Link to="/counter2">counter2</Link></li>
<li><Link to="/counter2/movie">counter2/movie</Link></li>
<li><Link to="/counter2/music">counter2/music</Link></li>
</ul>
{createRoutes(app, routesFn, true)}
</>
</ConnectedRouter>
</div>
)
}
export default RouterConfig;
src/routes/export.js
export {default as counter} from './counter';
src/routes/counter.js(一级路由)
import {dynamicWrapper, createRoutes} from './core';
import counter2Routes from '../components/Counter2/routes';
const routesCounter = app => {
let result = [
// 页面:/counter1
{
path: '/counter1',
title: 'page counter1',
//如果项目里全都是懒加载路由,可以只传component、model, 生成动态组件可以在createRoute里做。
component: dynamicWrapper(
app,
[import('../models/counter1'), import('../models/counter2')],
() => import('../components/Counter1'),
),
},
// 页面:/counter2
{
path: '/counter2',
indexRoute: '/counter2/movie',
title: 'page counter2',
component: dynamicWrapper(
app,
[import('../models/counter2')],
() => import('../components/Counter2'),
),
// 二级路由引用:方式1(单独一条一条生成路由)
// childRoutes: [
// counter2Routes.movie(app),
// counter2Routes.music(app),
// ],
// 二级路由引用:方式2(直接生成路由组)
childRoutes: createRoutes(app, counter2Routes, true),
},
]
return result;
}
export default routesCounter;
src/components/Counter2/routes.js (二级路由)
import { dynamicWrapper, createRoute } from "../../routes/core";
// /counter2/movie
const movie = (app) => ({
path: '/counter2/movie',
title: "page counter2-movie",
component: dynamicWrapper(
app,
[import("../../models/movie")],
() => import("./Movie")
),
});
// /counter2/music
const music = (app) => ({
path: '/counter2/music',
title: "page counter2-music",
component: dynamicWrapper(
app,
[],
() => import("./Music")
),
});
// 二级路由引用:方式1(单独一条一条生成路由)
// const routesCounter2 = {
// movie: (app) => createRoute(app, movie),
// music: (app) => createRoute(app, music),
// }
// 二级路由引用:方式2(直接生成路由组)
const routesCounter2 = app => ([
movie(app),
music(app),
])
export default routesCounter2;
src/components/Counter2/index.js
import {connect} from 'dva';
import {Switch} from "dva/router";
function Counter2(ps){
console.log('Counter2=>', ps);
return (
<div>
<div>{ps.number}</div>
<div>
<button onClick={() => ps.dispatch({type: 'counter2/add'})}>同步+</button>
<button onClick={() => ps.dispatch({type: 'counter2/asyncAdd'})}>异步+</button>
</div>
<div>
<h2>二级路由</h2>
<div>
{/* 二级路由引用:方式1(单独一条一条生成路由) */}
{/* <Switch>{ps.extraProps.childRoutes}</Switch> */}
{/* 二级路由引用:方式2(直接生成路由组) */}
{ps.extraProps.childRoutes}
</div>
</div>
</div>
)
}
export default connect(({counter2}) => counter2)(Counter2);
dva-loading
src/index.js
import dva from 'dva';
import history from './routes/history';
import dynamic from "dva/dynamic";
import createLoading from "dva-loading";
import RouterConfig from './routes';
// 创建dva实例
const app = dva({
history,
// onError: error => message.error(error.message),
});
// 引用插件
app.use(createLoading());
function Loading(){
return <div style={{position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, zIndex: 1000}}></div>
}
// -> loading 使用自定义loading组件
dynamic.setDefaultLoadingComponent(() => <Loading />);
// 定义根model(根model可应用于所有路由)(model可定义多个)
// app.model(require('./models/counter1').default);
// app.model(require('./models/counter2').default);
// 定义路由规则
// app.router 接收一个函数组件,回传参数是 {app, history}
// app.router(require('./routes').default);
app.router(RouterConfig);
// 启动
app.start('#root');
dva/fetch
异步请求库,输出 isomorphic-fetch 的接口。不和 dva 强绑定,可以选择任意的请求库。
import fetch from 'dva/fetch';
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
// 格式化数据
const parseJSON = response => response.json();
export async function get(url, params={}, onSuc, onErr, config={}) {
params.platform = 'pc';
let str = qsStringify(params, {prefix: '?'});
let allUrl = url + str;
return await fetch(allUrl, {
credentials: 'include', //fetch 默认不带 cookie 如果你想在fetch请求里附带cookies之类的凭证信息,可以将 credentials参数设置成 “include” 值。
headers: getHeaders(config),
})
.then(checkStatus)
.then(parseJSON)
.then((res) => successNetwork(allUrl, res, onSuc, onErr, config))
.catch((res) => errorNetwork(allUrl, res, onErr));
}
export async function post(url, params={}, onSuc, onErr, config={}) {
params.platform = 'pc';
let formData = new FormData();
if (params){
for(let k in params){
formData.append(k, params[k]);
}
}
return await fetch(url, {
method : 'POST',
credentials: 'include',
headers: getHeaders(config),
body: config.isBodyJSONStringify ? JSON.stringify(params) : formData,
})
.then(checkStatus)
.then(parseJSON)
.then((res) => successNetwork(url, res, onSuc, onErr, config))
.catch((res) => errorNetwork(url, res, onErr));
}
dva/saga
输出 redux-saga 的接口,主要用于用例的编写。(用例中需要用到 effects)