快速上手
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,为了简化开发体验,dva还额外内置了 react-router 和 fetch,所以也可
以理解为一个轻量级的应用框架。
#全局安装 dva-cli
cnpm install dva-cli -g
#创建新项目
dva new <project-name>
#使用 antd
安装 antd 和 babel-plugin-import。babel-plugin-import 实现 antd 的按需加载。
cnpm install antd babel-plugin-import --save
编辑 .webpackrc 使 babel-plugin-import 插件生效。
'extraBabelPlugins': [
['import', {'libraryName':'antd','libraryDirectory':'es','style':'css'}]
]
DVA核心API
输出文件
#dva
默认输出文件。
#dva/router
默认输出 react-router 接口, react-router-redux 的接口通过属性 routerRedux 输出。
import { Router, Route, routerRedux } from 'dva/router';
#dva/fetch
异步请求库,输出 isomorphic-fetch 的接口。不和 dva 强绑定,可以选择任意的请求库。
#dva/saga
输出 redux-saga 的接口,主要用于用例的编写。(用例中需要用到 effects)
#dva/dynamic
解决组件动态加载问题的 util 方法。
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
});
- app: dva 实例,加载 models 时需要
- models: 返回 Promise 数组的函数,Promise 返回 dva model
- component:返回 Promise 的函数,Promise 返回 React Component
dva API
index.js示例
import dva from 'dva';
const app = dva();
app.use({});
app.model(require('./models/products/products').default);
app.router(require('./router').default);
app.start('#root');
#const app = dva(opts)
创建应用,返回 dva 实例。(dva 支持多实例)
opts 包含如下属性:
-
initialState // 指定初始数据,优先级高于 model 中的 state,默认为空。
-
history // 指定给路由用的 history,默认 hashHistory。
如需使用 browserHistory,需安装history第三方库。
(1) 安装 history
cnpm install history
(2) 修改 history 配置
import { createBrowserHistory } from 'history'
const app = dva({
history: createBrowserHistory()
})
出于易用性的考虑,opts 里也可以配置所有 hooks。
2. app.use(hooks)
配置 hooks 或者注册插件,插件最终返回的是 hooks。
hooks 包含如下配置项:
onError,
onAction,
onReducer,
onEffect,
onStateChange,
onHmr,
extraReducers,
extraEnhancers
(1) onError((err, dispatch) => {})
effects 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态。
如果我们使用 antd 组件,那么最简单的全局错误处理通常会这么做:
import { message } from 'antd'
const app = dva({
onError(e) {
message.error(e.message, 3)
}
})
(2) onAction(fn | fn[ ])
在 action 被 dispatch 时触发,用于注册 redux 中间件。支持函数或函数数组格式。
例如我们要通过 redux-logger 打印日志:
import createLogger from 'redux-logger'
const app = dva({
onAction: createLogger(opts)
})
(3) onReducer(fn)
封装 reducer 执行,比如借助 redux-undo 实现 redo/undo :
import undoable from 'redux-undo'
const app = dva({
onReducer: reducer => {
return (state, action) => {
const undoOpts = {}
const newState = undoable(reducer, undoOpts)(state, action)
// 由于 dva 同步了 routing 数据,所以需要把这部分还原
return {
...newState,
routing: newState.present.routing
}
}
}
})
(4) onEffect(fn)
封装 effects 执行。比如 dva-loading 基于此实现了自动处理 loading 状态。
import createLoading from 'dva-loading'
app.use(createLoading(opts))
(5) onStateChange(fn)
state 改变时触发,可用于同步 state 到 localStorage,服务器端等。
(6) onHmr(fn)
热替换相关,目前用于 babel-plugin-dva-hmr
(7) extraReducers
指定额外的 reducer,比如 redux-form 需要指定额外的 form reducer:
import { reducer as formReducer } from 'redux-form'
const app = dva({
extraReducers: {
form: formReducer
}
})
3. app.model(model)
注册 model
model对象中包含5个重要的属性:
namespace,
state,
reducers,
effects,
subscriptions
(1) namespace
model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 . 的方式创建多层命名空间。
(2) state
reducer的初始值,优先级低于 dva() 实例对象的 initialState。
model 内的 state 指当前 model 的 state,外部的 state 指全局的 state。
外部使用 model 内部 state 的方式为:state. + namespace。
(3) reducers
reducer 用于处理同步操作,是唯一可以修改 state 的地方,由 action 触发。
reducer 接受两个参数,state 和 action,需要返回新的 state。
格式为 (state, action) => newState 或 [(state, action) => newState, enhancer]
(4)effects
effect 处理异步操作和业务逻辑,能与服务器交互,获取全局 state等。不直接修改 state。由 action 触发,可以触发action。
格式为 *(action, effects) => void 或 [*(action, effects) => void, { type }]
(5) subscriptions
subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。
在 app.start() 时被执行,数据源可以是当前时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history
路由变化等等。
格式为 ({ dispatch, history }, done) => unlistenFunction
注意:如果要使用 app.unmodel(),subscription 必须返回 unlisten 方法,用于取消数据订阅。
subscriptions 写法:
subscriptions: {
setup({ dispatch, history }) {
// 监听路由的变化,请求页面数据
return history.listen(({ pathname, search }) => {
const query = queryString.parse(search)
let list = []
if (pathname === 'todoList') {
dispatch({ type: 'save', payload: {list} })
}
})
}
}
4. app.unmodel(namespace)
取消 model 注册,清理 reducers, effects 和 subscriptions。
subscription 如果没有返回 unlisten 函数,使用 app.unmodel 会给予警告。️
5. app.router(({ history, app }) => RouterConfig)
注册路由表,注册路由
app.router(require('./router'))
如果我们想解决组件动态加载问题,我们的路由文件可以按照下面的写法来写:
import { Router, Switch, Route } from 'dva/router'
import dynamic from 'dva/dynamic'
function RouterConfig({ history, app }) {
const IndexPage = dynamic({
app,
component: () => import('./routes/IndexPage'),
})
const Products = dynamic({
app,
models: () => [import('./models/Products')],
component: () => import('./routes/Products'),
})
return (
<Router history={history}>
<Switch>
<Route exact path="/" component={IndexPage} />
<Route exact path="/products" component={Products} />
</Switch>
</Router>
)
}
export default RouterConfig
其中 dynamic(opts) 中 opts 包含三个配置项:
app: dva 实例,加载 models 时需要。
models: 返回 Promise 数组的函数,Promise 返回 dva model。
component:返回 Promise 的函数,Promise 返回 React Component。
6. app.start('selector')
app.start('#root')
启动应用,selector 可选,如果没有 selector 参数,会返回一个返回 JSX 元素的函数