链接: https://dvajs.com/knowledgemap/#effects
构成——通过model把一个领域的模型管理起来
- namespace 表示在全局state上的key
- state 初始值
- reducers 同步更新state
- effects 处理异步逻辑 常见数据请求
- subscriptions 订阅数据源
数据流向
通常通过交互行为 或者浏览器行为(比如路由跳转)触发 。当想通过此类行为改变数据的时候 首先可以通过dispatch发起一个action,如果是同步会直接通过reducers改变state, 如果是异步逻辑 则首先触发effcts 然后触发reducers改变state
单独使用
- state:不可直接修改state的某个值 以此来保证每次都是全新对象 在dva中可以通过app._store 查看顶部的state数据
const app = dva()
console.log(app._store)
- action、dispatch:改变state的唯一途径 触发action需要使用dispatch函数 必要带有type属性指明具体的行为 可通过props访问到dispatch
props.dispatch({
type: '/user/remove', // user 指的是namespace remove指的是reducers或者effects中的方法
payload: {} // 需要传递的参数
})
- reducer: 接受两个参数,第一个是state 第二个是传进来的payload 他必须是纯函数 同样的输入必须得到同样的输出
reducers: {
loginSuccess(state, {payload}) {
return {
...state,
user: payload
}
}
}
- effect:处理异步逻辑 常见数据请求 第一个参数是携带的参数 第二个是函数内部的处理函数
effects: {
*submit({ payload }, { call, put }) {
console.log(payload)
const response = yield call(register, payload); // call是用来发送数据请求的 第一个参数是关于api接口的回调函数 第二个参数是携带的参数
yield put({
type: 'registerHandle',
payload: response,
});
},
},
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'}) });
},
}
});
Router
import { Router, Route } from 'dva/router';
app.router(({history}) =>
<Router history={history}>
<Route path="/" component={HomePage} />
</Router>
);
相关语法
-
yield call 执行异步函数
-
yield put 发出一个action 类似于dispatch
-
select 从state里获取数据。
-
es6的Generator 在effect中的应用
-
一是在 function 后面,函数名之前有个 * ;
-
函数内部有 yield 表达式。
-
effects: { // 进入页面加载 *init({ payload }, { call, put, select }) { yield put({ type: 'getPageInfo', payload: { pageData: pageData.startPage(1, 10) } }); }, },
-
项目例子
1. 项目入口文件 src/index.js
import React from 'react';
import dva, { dynamic, router } from 'dva';
import createLoading from 'dva-loading';
import { createHashHistory } from 'history';
import createRoutes from '@/routes';
import { ConfigProvider } from 'antd';
const { Router } = router;
// -> 初始化
const app = dva({
history: createHashHistory({
basename: homepage.startsWith('/') ? homepage : ''
})
});
// -> 插件
app.use(createLoading());
app.use({ onError: config.exception.global });
// -> loading
dynamic.setDefaultLoadingComponent(() => config.router.loading);
// -> 注册全局模型
app.model(require('./models/global').default);
// -> 初始化路由
app.router(({ history, app }) => (
<ConfigProvider>
<Router history={history}>{createRoutes(app)}</Router> // 1
</ConfigProvider>
));
// -> Start
app.start('#root');
// export global
export default {
app,
store: app._store,
dispatch: app._store.dispatch
};
2. src/routes/index.js
import { createRoutes } from '@/utils/core';
import Login from './Login';
import Register from './Register';
import NotFound from './Pages/404';
import Dashboard from './Dashboard';
/**
* 主路由配置
*
* path 路由地址
* component 组件
* indexRoute 默认显示路由
* childRoutes 所有子路由
* NotFound 路由要放到最下面,当所有路由当没匹配到时会进入这个页面
*/
const routesConfig = app => [
{
path: '/sign',
title: '登录',
indexRoute: '/sign/login',
component: UserLayout,
childRoutes: [
Login(app),
Register(app),
NotFound()
]
},
{
path: '/',
title: '系统中心',
component: BasicLayout,
indexRoute: '/dashboard',
childRoutes: [
Dashboard(app),
]
}
];
export default app => createRoutes(app, routesConfig); // 2
3. src/utils/core.js
/**
* 生成动态组件
* @param {*} app
* @param {*} models
* @param {*} component
*/
export const dynamicWrapper = (app, models, component) =>
dynamic({
app,
models: () => models,
component
});
/**
* 生成一组路由
* @param {*} app
* @param {*} routesConfig
*/
export const createRoutes = (app, routesConfig) => { // 3
const routes = routesConfig(app)
.map(config => createRoute(app, () => config))
.reduce((p, n) => {
if (n.length) {
return [...p, ...n];
} else {
return p.concat(n);
}
}, []);
return <Switch>{routes}</Switch>;
};
/**
* 生成单个路由
* @param {*} app
* @param {*} routesConfig
*/
export const createRoute = (app, routesConfig) => { // 4
const {
component: Comp,
path,
indexRoute,
title,
exact,
...otherProps
} = routesConfig(app);
if (path && path !== '/') {
window.dva_router_pathMap[path] = { path, title, ...otherProps };
// 为子路由增加parentPath
if (otherProps.childRoutes && otherProps.childRoutes.length) {
otherProps.childRoutes.forEach(item => {
if (window.dva_router_pathMap[item.key]) {
window.dva_router_pathMap[item.key].parentPath = path;
}
});
}
}
// 把Redirect放到第一个
if (indexRoute && $$.isArray(otherProps.childRoutes)) {
otherProps.childRoutes.unshift(
<Redirect key={path + '_redirect'} exact from={path} to={indexRoute} />
);
}
const routeProps = {
key: path || $$.randomStr(4),
render: props => {
// 此处可以做路由权限判断
setDocumentTitle(title);
return <Comp routerData={otherProps} {...props} />
}
};
return <Route path={path} exact={!!exact} {...routeProps} />;
};
/**
* 设置页面title
* @param {*} title
*/
function setDocumentTitle(title) {
const documentTitle = config.htmlTitle ? config.htmlTitle.replace(/{.*}/gi, title) : title
if (documentTitle !== document.title) {
document.title = documentTitle;
}
}
4. routes/login/index.js
import { dynamicWrapper, createRoute } from '@/utils/core';
const routesConfig = (app) => ({
path: '/sign/login',
title: 'Login',
component: dynamicWrapper(app, [import('./model')], () => import('./components')) // 生成动态的组件
});
export default (app) => createRoute(app, routesConfig); // 导出当前路由react组件 以及model
5. routes/login/server/index.js
import axios from 'axios'
export async function member(payload) {
return axios.get('接口',参数)
}
6. routes/login/model/index.js
import { member } from '../service'
export default {
namespace: 'member',
state: {
productItem: {},
},
effects: {
*init({ payload }, { call, put }) {
try {
const { data: { data }} = yield call(member, payload)
yield put({
type: 'setData',
payload: {
data: data[0]
}
})
} catch(e) {
console.log(e)
}
}
},
reducers: {
setData(state, { payload }) {
return {
...state,
productItem: payload.data
}
}
}
}
7. routes/login/components/index.js
import React, { Component } from 'react'
import Taro from '@tarojs/taro'
import { Image, View } from '@tarojs/components'
import { connect } from 'react-redux'
import './member.scss'
import { AtButton } from 'taro-ui'
interface ItemProps {
dispatch: any,
productItem: any,
orderItem: any,
}
interface ItemState { }
class Member extends Component<ItemProps, ItemState> {
componentDidMount() {
this.props.dispatch({
type: 'member/init',
payload: {
page: 1,
perPage: 3,
city_id: '257'
}
})
}
render() {
const { productItem } = this.props
return (
<view className="member">
{
productItem && productItem.products && productItem.products.map(item => (
<View className='vip-product'>
<View>
{item.item_attribute_detail.attribute_value}
</View>
<View style="color:red">¥{item.price}元</View>
</View>
))
}
</View>
</view>
)
}
}
function getState({ member: { productItem } }) {
return {
productItem
}
}
export default connect(getState)(Member)