1. create-react-app只是创建了一个最基本的reactdemo,需要手动安装 react-router,redux等
  2. DVA 是基于 redux、redux-saga 和 react-router 的轻量级前端数据流框架
    1. dva = redux + redux-saga + react-router
    2. dva没有新概念,只是封装了redux
    3. react视图层框架 + dva数据流框架 = 复杂的SPA应用架构
  3. dva特点
    1. 数据共享 models,数据可以 connect任何组件
    2. 数据和视图逻辑分离,effects
    3. 异步请求

https://github.com/xlsdg/dva-antd-starter/blob/master/src/router.jsx
https://github.com/ant-design/create-react-app-antd

dva是一个非常轻量级的封装

  1. react
  2. react-router-dom
  3. connected-react-router
  4. redux
  5. redux-saga

dva-cli创建项目

  1. 全局安装 dva-cli,用 dva-cli 创建项目
  2. 项目目录包括mock,services,models ```bash

    全局安装 dva-cli

    npm install dva-cli -g

yarn global add dva-cli

dva -v # 查看版本号

dva-cli version 0.10.1

dva创建项目

dva new mydva cd mydva npm start

  1. 3. 出现这个界面,就说明 dva项目创建成功
  2. ![dva-demo.png](https://cdn.nlark.com/yuque/0/2020/png/112859/1591320062359-add6647b-35ed-40f1-8f85-ca39f1ce14cc.png#height=605&id=MqqxG&originHeight=605&originWidth=692&originalType=binary&ratio=1&size=90686&status=done&style=none&width=692)
  3. <a name="27JTr"></a>
  4. ### dva目录结构
  5. ```jsx
  6. .roadhogrc.mock.js Mock配置文件
  7. .webpackrc 自定义的webpack配置文件,JSON格式
  8. 如果需要 JS格式,修改为 .webpackrc.js

dva数据流程

  1. 输入 URL渲染对应组件,用户在 view层组件中,触发 一个action
  2. 组件 dispatch(action) 触发 model里面的函数
    a. 同步,直接进入 reducer里面的方法
    b. 异步,进入 effects里面的方法,fetch获取接口数据
  3. 组件通过 connect连接数据

dva.png

  1. app.model({
  2. namespace: 'products',
  3. state: {
  4. list: [],
  5. loading: false,
  6. },
  7. subscriptions: [
  8. function(dispatch) {
  9. dispatch({type: 'products/query'});
  10. },
  11. ],
  12. // 异步代码,要 dispatch(action)到 reducers修改 state
  13. // 类似 vue的 action,commit() 到 mutation修改 state
  14. effects: {
  15. ['products/query']: function*() {
  16. yield call(delay(800));
  17. yield put({
  18. type: 'products/query/success',
  19. payload: ['ant-tool', 'roof'],
  20. });
  21. },
  22. },
  23. reducers: { // 同步代码,类似 vue的 mutation
  24. ['products/query'](state) {
  25. return { ...state, loading: true, };
  26. },
  27. ['products/query/success'](state, { payload }) {
  28. return { ...state, loading: false, list: payload };
  29. },
  30. },
  31. });

dva概念

  1. models
  2. connect
  3. dispatch
  4. action
  5. reducer

src.png

models

  1. /src/models/indexPage.js
  2. namespace 命名空间
    1. 只能用字符串
    2. 大型项目有很多 model,通过 namespace区分
  3. state 初始值
    1. 表示当前状态,可以设置初始值
    2. 优先级低于 app.model({ state})
  4. reducers 同步操作
    1. (state, action) => newState;reducer修改 state
    2. 处理同步操作,修改 state
  5. effects:ajax异步操作,副作用
    1. ajax请求后台接口,不能修改 state
    2. dispatch(action) 调用 reducer修改 state
    3. 类似 vue的 actions - commit(‘actionName’, {}) 到 mutations里面,mutation再修改 store
    4. IO异步,操作数据库等
  6. action
    1. 是 reducers,effects的触发器
  1. import { getUsers } from '../services/user'
  2. export default {
  3. namespace: 'indexPage', // 命名空间,通过 this.props.indexPage获取数据
  4. // 初始化的数据
  5. state: {
  6. data: [ // tbody 表格的数据
  7. {
  8. "key": "10", "name": "张飞宇", "age": 12
  9. }
  10. ],
  11. columns: [ // thead 表头
  12. {
  13. title: '姓名',dataIndex: 'name',key: 'name',
  14. },
  15. {
  16. title: '年龄',dataIndex: 'age',key: 'age',
  17. }
  18. ]
  19. },
  20. subscriptions: {
  21. setup({ dispatch, history }) { // eslint-disable-line
  22. }
  23. },
  24. effects: { // generator函数
  25. *add({ payload }, { call, put }) {
  26. const users = yield call(getUsers, {}) // services/user,要import导入
  27. yield put({
  28. type: 'addUser', // effects 提交的是 reducers的方法
  29. payload: { data: users.data.data }
  30. })
  31. }
  32. },
  33. reducers: { // store依赖的清单
  34. addUser(state, action) {
  35. const data = [...action.payload.data, ...state.data]
  36. return { ...state, data }
  37. }
  38. }
  39. }

connect

  1. 装饰器写法,给装饰对象赋予不具备的能力
  2. connect让组件获取 model中的数据,触发model中的方法
    1. mapStateToProps,把 dva中的 state通过 props 传递个组件
    2. mapDispatchToProps,通过 props把 dispatch方法传递给组件
  1. connect()()
  2. conect(mapStateToProps, mapDispatchToProps)(Component)

dispatch

  1. dispatch是个纯函数,将 action发送给 store
  2. 组件触发 model的唯一方法
  3. diapatch接收一个对象作为参数,这个参数称之为 action
    1. store,全局唯一的数据源;收到 action后会更新数据
    2. view,react的UI层,从 store获取数据,渲染成 html代码
    3. 只要 store有变化,view就会自动更新
      1. const action = {
      2. type: 'changeValue',
      3. payload: ev.target.value
      4. }
      5. dispatch(action)

action

  1. action是描述 组件/UI事件的一个对象
  2. 必须包含 type字段,payload是参数
  3. actionTypes.js,保存 action常量;推荐用 constants.js保存常量
  4. actionReducer.js,抽离所有 action到一个页面
    1. {
    2. type: 'changeValue',
    3. payload: ev.target.value
    4. }

reducer

  1. 每个 reducer都是一个纯函数,接收 (state, action);没有副作用
  2. 最多一层嵌套,保持 state 的扁平化
  3. 深层嵌套会让 reducer 很难写和难以维
    1. app.model({
    2. namespace: 'todos',
    3. state: {},
    4. reducers: {
    5. remove(state, { payload: id }) {
    6. return state.filter(todo => todo.id !== id)
    7. },
    8. // state嵌套太深,不好维护
    9. more(state, { payload: todo }) {
    10. const todos = state.a.b.todos.concat(todo)
    11. const b = { ...state.a.b, todos }
    12. const a = { ...state.a, b }
    13. return { ...state, a }
    14. }
    15. }
    16. }

effects

  1. put 触发 action
  2. call 调用异步逻辑,支持 promise
    1. call & put是 redux-saga的方法
  3. select 从 state里面获取数据
  4. 函数必须是 Generator函数, 返回的是迭代器,通过 yield 关键字实现暂停功能
  1. import {AsyncAdd} from '../request'
  2. app.model({
  3. namespace: 'todos',
  4. state: { todo: [], value: 'ok' },
  5. effects: {
  6. *addRemote({ payload: todo }, { put, call, select }) {
  7. const res = yield call(AsyncAdd, todo);
  8. const value = select(state => state.value)
  9. yield put({ type: 'add', payload: {data: res.data, value} });
  10. },
  11. },
  12. reducer: {
  13. add (state, action) {
  14. }
  15. }
  16. })

subscriptions

  1. 1. 用于收集其他来源的 action,是个订阅,订阅一个数据源
  2. 2. 然后 dispatch(action), app.start() 被执行
  3. 3. 数据源可以是:
  4. 1. 当前的时间
  5. 2. 服务器的 websocket 连接
  6. 3. keyboard 输入操作
  7. 4. 滚动条
  8. 5. geolocation 变化
  9. 6. history 路由变化等
  10. 4. 如果要使用 app.unmodel(),subscription 必须返回 unlisten 方法,用于取消数据订阅
  1. app.model({
  2. namespace: 'todo',
  3. // app.start() 时被执行
  4. subscriptions: {
  5. // 监听 history变化,当进入 / 触发 load action
  6. setup ({history, dispatch}) {
  7. return history.listen(({pathname}) => {
  8. if (pathname === '/') dispatch({ type: 'load' })
  9. })
  10. },
  11. onClick ({ dispatch }) {
  12. document.addEventListener('click', () => {
  13. dispatch({ type: 'save' })
  14. })
  15. },
  16. backHistory ({ dispatch, history }) {
  17. history.lisen(location => {
  18. console.log('location')
  19. })
  20. }
  21. }
  22. })

app

  1. const app = dva({
  2. history, // 指定给路由用的 history,默认是 hashHistory
  3. initialState, // 指定初始数据,优先级高于 model 中的 state
  4. onError, // effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态。
  5. onAction, // 在 action 被 dispatch 时触发
  6. onStateChange, // state 改变时触发,可用于同步 state 到 localStorage,服务器端等
  7. onReducer, // 封装 reducer 执行。比如借助 redux-undo 实现 redo/undo
  8. onEffect, // 封装 effect
  9. onHmr, // 热替换相关
  10. extraReducers, // 指定额外的 reducer,比如 redux-form 需要指定额外的 form reducer
  11. extraEnhancers, // 指定额外的 StoreEnhancer ,比如结合 redux-persist 的使用
  12. });
  13. // hashHistory 转 BrowserHistory
  14. import createHistory from 'history/createBrowserHistory';
  15. const app = dva({
  16. history: createHistory(),
  17. });
  18. // dva-loading
  19. import createLoading from 'dva-loading';
  20. app.use(createLoading(opts));
  21. app.model({
  22. namespace: 'todo',
  23. state: {
  24. value: '',
  25. data: []
  26. },
  27. reducers: {
  28. add (state, { payload: todo }) {
  29. // 保存数据到 state
  30. return [...state, data: todo]
  31. }
  32. },
  33. effects: {
  34. *save (state, action) {
  35. yield call(saveServer, state.todo)
  36. yield put({ type: 'add', payload: state.todo })
  37. }
  38. }
  39. })

父子组件通信

  1. 父组件管理 state,子组件通过 props获取 state
  2. 修改 state;子组件调用父组件的方法来 setState
    1. 方法通过属性方式传递给子组件

dva数据流程

  1. index.js入口
  2. 通过 url匹配 routes里面的组件;加载 components里面的公共组件
  3. 组件 dispatch(action)
  4. models 通过 action 改变 state;在services里面 ajax请求后台接口
  5. 通过 connect重新渲染 routes里面的组件

dva数据流.png

dva-cli项目目录

  1. 默认安装的npm包: dva & react & react-dom
  2. 代码规范 eslint & husky
    1. {
    2. "private": true,
    3. "scripts": {
    4. "start": "roadhog server",
    5. "build": "roadhog build",
    6. "lint": "eslint --ext .js src test",
    7. "precommit": "npm run lint"
    8. },
    9. "dependencies": {
    10. "dva": "^2.4.1",
    11. "react": "^16.2.0",
    12. "react-dom": "^16.2.0"
    13. },
    14. "devDependencies": {
    15. "babel-plugin-dva-hmr": "^0.3.2",
    16. "eslint": "^4.14.0",
    17. "eslint-config-umi": "^0.1.1",
    18. "eslint-plugin-flowtype": "^2.34.1",
    19. "eslint-plugin-import": "^2.6.0",
    20. "eslint-plugin-jsx-a11y": "^5.1.1",
    21. "eslint-plugin-react": "^7.1.0",
    22. "husky": "^0.12.0",
    23. "redbox-react": "^1.4.3",
    24. "roadhog": "^2.5.0-beta.4"
    25. }
    26. }

antd安装

  1. npm 安装 antd, babel-plugin-import 按需加载 antd样式和脚本
  2. 编辑 .webpackrc,配置 antd按需加载
    1. npm install antd babel-plugin-import

mock数据

  1. .roadhogrc.mock.js
  2. mock 数据太多时,可以拆分后放到 ./mock 文件夹中
    1. 然后在 .roadhogrc.mock.js引入
  3. api参考 express4.*
  1. export default {
  2. // 请求 /api/users 时会返回 JSON 格式的数据
  3. 'GET /api/users': { users: [{ username: 'admin' }] },
  4. // 自定义函数
  5. 'POST /api/users': (req, res) => { res.end('OK'); },
  6. }

组件动态加载

  1. 最好封装一下,每个路由组件都这么写一遍很麻烦
  1. import dynamic from 'dva/dynamic';
  2. const UserPageComponent = dynamic({
  3. app,
  4. models: () => [
  5. import('./models/users'),
  6. ],
  7. component: () => import('./routes/UserPage'),
  8. })

dva-loading

  1. 处理 loading 状态的 dva 插件,基于 dva 的管理 effects 执行的 hook 实现
  2. dva-loading会在 state 中添加一个 loading 字段,该字段可自定义
  3. 自动处理网络请求的状态,不需再去写 showLoadinghideLoading 方法
  4. ./src/index.js 中引入使用即可
    1. opts 仅有一个 namespace 字段,默认为 loading
  1. import createLoading from 'dva-loading';
  2. const app = dva();
  3. app.use(createLoading(opts))

models

  1. models是dva的数据流核心
  2. 可以理解为 redux、react-redux、redux-saga 的封装
  3. 通常一个模块对应一个model,基本的格式

dva 6个 api

  1. namespace 当前model的命名空间
    1. 全局state上的一个属性,必须是字符串
    2. 不能点点点,创建多层命名空间;错误的用法 index.page.user
    3. 组件中触发 action,要带上命名空间,,{type: 'user/fetch'}
    4. 当前model中触发 action不需要命名空间,{type: 'save'}
  2. state 初始值
  3. reducer 处理同步
    1. 纯函数,不可变值;类似 redux的 reducer
    2. 唯一可以修改 state的,由 action触发;
    3. 有 state & action2个形参,save(state, action) {}
  4. effects 处理异步
    1. 不能直接修改 state,能触发 action
    2. 只能是 generator函数
    3. 有 action & effects2个形参 fetch({ payload }, { call, put }) {}
    4. effects包含 call, put, select 3个形参
      1. call 请求ajax接口
      2. put 触发 action
      3. select 用于从 state中获取数据
  5. subscriptions 订阅数据
    1. 根据情况,dispatch(action)
    2. 格式:({ dispatch, history }, done) => {}

model流程演示

  1. 监听路由变化,当进入 /user 页面时,执行 effects中的 fetch ajax请求,获取后台数据
  2. 然后 fetch中触发 reducers中的 save方法,将后台数据保存到 state中
  1. import { getUser } from '../services/user.js' // 统一管理 ajax接口
  2. export default {
  3. namespace: 'user',
  4. state: {
  5. usre: {}
  6. },
  7. subscriptions: {
  8. setup({ dispatch, history }) { // eslint-disable-line
  9. return history.listen(({pathname}) => {
  10. if (pathname === '/usre') dispatch({type: 'fetch'})
  11. })
  12. }
  13. },
  14. effects: {
  15. *fetch({ payload }, { call, put }) { // eslint-disable-line
  16. const res = yield put(getUser, payload.data)
  17. yield put({ type: 'save', data: res.data })
  18. },
  19. },
  20. reducers: {
  21. save(state, action) {
  22. return { ...state, ...action.payload }
  23. }
  24. }
  25. }

services/user.js

  1. 用 axios也行,哪个熟练用哪个
  1. import request from '../utils/request'
  2. export const getUsers = () => request('/api/users')

dva数据流管理

components新增组件

  1. /src/components
    1. Header.js 公共导航栏
    2. Layout.js 布局
  1. // Header.js
  2. import React, { Component } from "react"
  3. import { Menu } from "antd"
  4. import { Link } from "dva/router"
  5. class Header extends Component {
  6. render() {
  7. return (
  8. <Menu
  9. theme="dark"
  10. mode="horizontal"
  11. defaultSelectedKeys={["1"]}
  12. style={{ lineHeight: "64px" }}
  13. >
  14. <Menu.Item key="1">
  15. <Link to="/">首页</Link>
  16. </Menu.Item>
  17. <Menu.Item key="2">
  18. <Link to="/list">ListPage</Link>
  19. </Menu.Item>
  20. <Menu.Item key="3">
  21. <Link to="/about">AboutPage</Link>
  22. </Menu.Item>
  23. </Menu>
  24. )
  25. }
  26. }
  27. export default Header
  28. // Layout.js
  29. import React, { Component } from "react"
  30. import Header from "./Header"
  31. class Layout extends Component {
  32. render() {
  33. const { children } = this.props
  34. return (
  35. <div>
  36. <Header />
  37. <div style={{ background: "#fff", padding: '24px' }}>{children}</div>
  38. </div>
  39. )
  40. }
  41. }
  42. export default Layout

page.png

routes新增页面

  1. /src/routes/
    1. list/ListPage.js
    2. about/AboutPage.js ```jsx // list/ListPage.js import React, { Component } from ‘react’ import { connect } from ‘dva’

class ListPage extends Component { render () { return (

list/ListPage.js this.props.children 类似于 vue 的slot

) } } ListPage.propTypes = {} export default connect()(ListPage)

// about/AboutPage.js import React, { Component } from ‘react’ import { connect } from ‘dva’

const AboutPage = props => { return

about/AboutPage.js props.children 类似于 vue 的slot

} AboutPage.propTypes = {} export default connect()(AboutPage)

  1. <a name="104144f4"></a>
  2. ### /router.js 连接路由
  3. 1. /router.js 把新建的页面给 import引入
  4. 2. 完成这个操作后,点击路由就会切换不同的页面
  5. ```jsx
  6. import React from 'react';
  7. import { Router, Route, Switch } from 'dva/router';
  8. import IndexPage from './routes/IndexPage';
  9. // newPage
  10. import Layout from './components/Layout'
  11. import ListPage from './routes/list/ListPage'
  12. import AboutPage from './routes/about/AboutPage'
  13. function RouterConfig({ history }) {
  14. return (
  15. <Router history={history}>
  16. <Layout>
  17. <Switch>
  18. {/*
  19. 路径为 /的时候匹配到IndexPage;exact,精确匹配
  20. 如果不加 exact,则/list, /about, /whatever都会匹配到IndexPage
  21. exact精确匹配,只有输入 /的时候才会匹配到 IndexPage
  22. */}
  23. <Route path="/" exact component={IndexPage} />
  24. <Route path="/list" exact component={ListPage} />
  25. <Route path="/about" exact component={AboutPage} />
  26. </Switch>
  27. </Layout>
  28. </Router>
  29. );
  30. }
  31. export default RouterConfig;

/models创建模型

  1. /srcmodels/IndexPage.js
  2. namespace 命名空间
    1. 组件中,用 this.props.indexPage 获取 state的数据
  1. import { getUsers } from '../services/user'
  2. export default {
  3. namespace: 'indexPage', // 命名空间,通过 this.props.indexPage获取数据
  4. // 初始化的数据
  5. state: {
  6. data: [ // tbody 表格的数据
  7. {
  8. "key": "10", "name": "张飞宇", "age": 12
  9. }
  10. ],
  11. columns: [ // thead 表头
  12. {
  13. title: '姓名',
  14. dataIndex: 'name',
  15. key: 'name',
  16. },
  17. {
  18. title: '年龄',
  19. dataIndex: 'age',
  20. key: 'age',
  21. },
  22. ]
  23. },
  24. subscriptions: {
  25. setup({ dispatch, history }) { // eslint-disable-line
  26. },
  27. },
  28. effects: { // generator函数
  29. *fetch({ payload }, { call, put }) { // eslint-disable-line
  30. yield put({ type: 'save' });
  31. },
  32. *add({ payload }, { call, put }) {
  33. const users = yield call(getUsers, {}) // services/user,要import导入
  34. yield put({
  35. type: 'addUser', // effects 提交的是 reducers的方法
  36. payload: { data: users.data.data }
  37. })
  38. }
  39. },
  40. reducers: { // store依赖的清单
  41. save(state, action) {
  42. return { ...state, ...action.payload };
  43. },
  44. addUser(state, action) {
  45. const data = [...action.payload.data, ...state.data]
  46. return { ...state, data }
  47. }
  48. }
  49. }

routes页面绑定models数据

  1. /src/routes/IndexPage.js
  1. import React from "react"
  2. import { connect } from "dva"
  3. import { Button, Table } from "antd"
  4. // IndexPage用 models/indexPage.js的数据,要用 connect连接
  5. const IndexPage = props => {
  6. const { dispatch, indexPage: { data, columns } } = props
  7. function changeData() {
  8. dispatch({
  9. type: 'indexPage/add', // 调用 models/indexPage.js的 add
  10. payload: {}
  11. })
  12. }
  13. // const { data, columns } = props.indexPage
  14. return (
  15. <div>
  16. <Button type="primary" onClick={changeData}>获取数据</Button>
  17. <Table columns={columns} dataSource={data} />
  18. </div>
  19. );
  20. };
  21. IndexPage.propTypes = {}
  22. // connect(models/数据)(routes/组件)
  23. // models/indexpage.js中的 state数据, 就关联到 routes/IndexPage.js
  24. export default connect(
  25. ({ indexPage }) => ({ indexPage }) // 返回一个对象
  26. )(IndexPage)

services ajax请求

  1. /src/services/user.js
  2. request请求库,用自己熟练的,比如 axios
  3. 在 /src/utils/request.js 里面封装 axios请求
  1. import request from '../utils/request'
  2. // import axios from 'axios
  3. export const getUsers = () => request('/api/users')

mock数据

  1. .roadhogrc.mock.js 设置mock数据,或自己搭建服务器
    1. const users = [
    2. {
    3. "key": "1", "name": "王达美", "age": 30
    4. },
    5. {
    6. "key": "2", "name": "刘高三", "age": 20
    7. }
    8. ]
    9. export default {
    10. 'GET /api/users': { data: users }
    11. }

组件 dispatch(action)

  1. dva规范:必须通过触发 actoin来改变 state
  2. 同步修改:直接调用model中的reducer
  3. ajax异步:先触发effects,然后调用reducer
    1. dispatch(action) 到 reducer,在 reducer里面改变 state
  1. // 1 IndexPage.js 组件 dispatch(action)
  2. const IndexPage = props => {
  3. const { dispatch, indexPage: { data, columns } } = props
  4. function changeData() {
  5. dispatch({
  6. type: 'indexPage/add', // 触发 models/indexPage.js的 add方法
  7. payload: {}
  8. })
  9. }
  10. return (
  11. <div>
  12. <Button type="primary" onClick={changeData}>获取数据</Button>
  13. <Table columns={columns} dataSource={data} />
  14. </div>
  15. )
  16. }

/models/indexPage.js

  1. effects 可以获取全局的 state,也可以触发 action
    1. yield关键字表示每一操作
    2. call() 表示调用 异步函数
    3. put() 表示 dispacth(action)
  2. 组件内调用 models要加上命名空间
    1. 当前 model调用不需要加 命名空间
  3. effects 里面的函数必须是 generator函数 ```jsx import { getUsers } from ‘../services/user’

export default { namespace: ‘indexPage’, // 命名空间,通过 this.props.indexPage获取数据

// 初始化的数据 state: { data: [], // tbody 表格的数据 columns: [ // thead 表头 { title: ‘姓名’,dataIndex: ‘name’,key: ‘name’ }, { title: ‘年龄’,dataIndex: ‘age’,key: ‘age’ }, ] }, subscriptions: { setup({ dispatch, history }) { // eslint-disable-line } },

effects: { // generator函数 *fetch({ payload }, { call, put }) { // eslint-disable-line yield put({ type: ‘save’ }) },

  1. *add({ payload }, { call, put }) {
  2. const res = yield call(getUsers, {}) // services/user,要import导入
  3. yield put({
  4. type: 'ADDUSER', // effects 提交的是 reducers的方法
  5. payload: { data: res.data }
  6. })
  7. }

},

reducers: { // store依赖的清单 save(state, action) { return { …state, …action.payload }; },

  1. [ADDUSER'](state, action) {
  2. const data = [...action.payload.data, ...state.data]
  3. return { ...state, data }
  4. }

} }

  1. 到这里,完成了一个基本的 dva数据流程,从新建页面到 connect绑定数据的异步操作。
  2. <a name="index.js"></a>
  3. ## app
  4. 1. index.js中的 app 就是 dva的实例,实例化 dva的参数有:
  5. 1. history
  6. 2. initialState 初始数据,优先级高于 model state,默认 {}
  7. 3. onError
  8. 4. onActoin
  9. 5. onReducer
  10. 6. onEffect
  11. 7. onHmr
  12. 8. extraReducers
  13. 9. extraEnhancers
  14. 2. indexjs留出了 model 模型,router路由的位置,开发中
  15. 1. 所有的 model模型都要在 index.js注册,才能用 connect关联组件
  16. ```jsx
  17. import dva from 'dva';
  18. import './index.css';
  19. // 1. Initialize
  20. const app = dva();
  21. // 2. Plugins
  22. // app.use({});
  23. // 3. Model 所有的model模型都要在这注册,才能用connect跟组件关联起来
  24. app.model(require('./models/indexPage').default);
  25. // 4. Router 引用了router.js
  26. app.router(require('./router').default);
  27. // 5. Start
  28. app.start('#root');

app参数

  1. const app = dva({
  2. history,
  3. initialState,
  4. onError,
  5. onAction,
  6. onStateChange,
  7. onReducer,
  8. onEffect,
  9. onHmr,
  10. extraReducers,
  11. extraEnhancers,
  12. })

app.use

history

  1. history是给路由用的,默认 hashHistory
  2. 要用 browserHistory,安装 history,然后在 index.js引入
  1. import dva from 'dva';
  2. import createHistory from 'history/createBrowserHistory';
  3. const app = dva({
  4. history: createHistory()
  5. });

connect-router.js

  1. 写完 model 和组件后,需要将 model 和组件连接起来
  2. connect就是 react-redux的 connect
  3. connect连接组件后,默认参数 dispatch, state,还有 location, history
  1. import React from 'react'
  2. import { connect } from 'dva'
  3. const User = ({ dispatch, user }) => {
  4. return <div className="root"></div>
  5. }
  6. export default connect(
  7. ({ user }) => ({ user }) // 返回一个对象
  8. )(User)

错误处理

  1. dva 里,effects 和 subscriptions 的抛错全部会走 onError hook
  2. 在 onError 里统一处理错误,全局捕获effects & subscriptions的错误
  3. 对某些 effects进行错误捕获,需要在 effect 内部 try {} catch() {}
  1. // 全局错误处理
  2. const app = dva({
  3. onError(err, dispatch) {
  4. console.error(err);
  5. }
  6. })
  7. // 函数内部错误处理
  8. app.model({
  9. effects: {
  10. *addRemote() {
  11. try {
  12. // Your Code Here
  13. } catch(e) {
  14. console.log(e.message)
  15. }
  16. }
  17. }
  18. })

ajax异步请求

  1. dva集成了 isomorphic-fetch处理异步请求
  2. ./src/utils/request.js,简单封装了 fetch
  3. https://github.com/matthew-andrews/isomorphic-fetch
  4. 推荐用 axios,多个项目保持一致性,不推荐用 fetch
  5. fetch细节
    1. fetch请求默认不带cookie,需要设置fetch(url,{credentials: ‘include’})
    2. 服务器返回400、500等错误码不会reject,返回成功态 resolve
    3. 只有网路错误请求不能完成时才会reject
    4. 基于 Promise,支持 async/await
  1. npm install --save isomorphic-fetch es6-promise

proxy代理

  1. create-react-app创建的项目,在 package.json里设置代理
  1. "proxy": "http://api.xxxx.com" // 单个域名的大力
  2. // 多个子域的代理
  3. "proxy": {
  4. "/api/v2": {
  5. "target": "http://v2.lulongwen.com",
  6. "changeOrigin":true
  7. },
  8. "/api/v1":{
  9. "target":"http://v1.lulongwen.com",
  10. "changeOrigin":true
  11. }
  12. }
  1. antd 代理,package.json同级目录,创建 .roadhogrc文件
  1. {
  2. "entry": "src/index.js",
  3. "extraBabelPlugins": [
  4. "transform-runtime",
  5. "transform-decorators-legacy",
  6. "transform-class-properties",
  7. ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
  8. ],
  9. "env": {
  10. "development": {
  11. "extraBabelPlugins": [
  12. "dva-hmr"
  13. ]
  14. }
  15. },
  16. "externals": {
  17. "g2": "G2",
  18. "g-cloud": "Cloud",
  19. "g2-plugin-slider": "G2.Plugin.slider"
  20. },
  21. "ignoreMomentLocale": true,
  22. "theme": "./src/theme.js",
  23. "proxy": {
  24. "/api": {
  25. "target": "http://api.xxxx.com/",
  26. "changeOrigin": true
  27. }
  28. }
  29. }

roadhog

  1. 包含 dev, build, test的命令行工具
  2. 基于 react-dev-utils,和 create-react-app基本一致,可配置版的 create-react-app
  1. npm i roadhog -g
  2. roadhog -v

dva参考资料

  1. https://github.com/sorrycc/dva-tutorial
  2. https://github.com/dvajs/awesome-dva
  3. 支付宝前端应用架构的发展和选择: 从 roof 到 redux 再到 dva