dva2.x的主要缺点

  • react-router,不支持 hooks,hooks重构项目要注意;不是最新的 react-router-dom5.x
    • hooks memo 和dva connect使用 bug
    • hooks 不能使用 useHistory
  • history路由报错
  • less报错,less版本必须是2.x, less-loader必须是 4.x,否则报 less编译的错误
    1. {
    2. "less": "^2.7.3",
    3. "less-loader": "^4.1.0",
    4. }
    dva更新文档 https://github.com/dvajs/dva/issues/2208

项目结构

  1. ├── .editorconfig #
  2. ├── .eslintrc # Eslint config
  3. ├── .gitignore #
  4. ├── .roadhogrc # Roadhog config
  5. ├── mock # 数据mock的接口文件
  6. ├── package-lock.json
  7. ├── package.json
  8. ├── public
  9. └── index.html
  10. └── src # 项目源码目录
  11. ├── assets
  12. ├── components # 项目组件
  13. ├── index.css # css入口
  14. ├── index.js # 项目入口文件
  15. ├── models # 数据模型
  16. ├── router.js # 路由配置
  17. ├── routes # 页面路由组件
  18. ├── services # API接口管理
  19. └── utils # 工具函数
  20. /src/.
  21. ├── assets
  22. └── yay.jpg
  23. ├── components
  24. └── Example.js
  25. ├── index.css
  26. ├── index.js
  27. ├── models
  28. └── example.js
  29. ├── router.js
  30. ├── routes
  31. ├── IndexPage.css
  32. └── IndexPage.js
  33. ├── services
  34. └── example.js
  35. └── utils
  36. └── request.js

src/index.js

  1. import dva from 'dva';
  2. import createLoading from 'dva-loading';
  3. import createHistory from 'history/createBrowserHistory';
  4. import { persistStore, persistReducer } from 'redux-persist'
  5. import storage from 'redux-persist/lib/storage'
  6. import 'moment/locale/zh-cn';
  7. import { name } fom './package.json';
  8. import './polyfill';
  9. import './index.less';
  10. // redux持久化配置
  11. const persistConfig = {
  12. key: 'root',
  13. storage: storage,
  14. blacklist: []
  15. }
  16. // 1. Initialize
  17. const app = dva({
  18. history: createHistory({
  19. basename: name,
  20. }),
  21. onError(error) {
  22. // 捕捉effect中执行的报错,subscriptions 中通过done触发的错误
  23. console.error(error.message);
  24. },
  25. onAction() {
  26. return (next) => (action) => next(action)
  27. },
  28. // 可监听state状态的变化
  29. onStateChange() { },
  30. onReducer(reducer) {
  31. return persistReducer(persistConfig, reducer)
  32. }
  33. });
  34. // 2. Plugins
  35. app.use(createLoading());
  36. // 3. Router
  37. app.router(require('./router').default);
  38. // 4. Start
  39. app.start('#root');
  40. window.onload = () => persistStore(app._store)

models

每个路由都对应一个 model层
在model定义好这个路由的initialstate、reducers、sagas、subscriptions
然后组件里面,connect()(组件)
在组件里 dispatch(action) 发起调用

  • 同步action时,type写成’namespace/reducer’ ,dva就调用 reducers下,对应名字的reducer更新state
  • 异步action,type就写成’namespace/saga’,dva就调用 effects下,对应名字的 saga,
  • 然后再 effects里面,put(action),同步更新state

    1. export default {
    2. // 分割的路由,对应 combine到root Reducer里的名字,这里是state.user
    3. namespace: 'user',
    4. state: { // 初始的 state
    5. data: [],
    6. loading: false,
    7. },
    8. reducers: {
    9. save(state, action) {
    10. return { ...state, ...action.payload }
    11. }
    12. },
    13. // saga里的effects,里面的各种处理异步操作的saga
    14. effects: {
    15. *asyncGetUser(action, effect) {
    16. const { payload } = action;
    17. const { put, call, select } = effect;
    18. const res = yield call(axios.post('url'), payload);
    19. yield put({ type: 'save', payload: { data: res.data } });
    20. }
    21. },
    22. // 监听路径变化,当路由为 user时,dispatch一个获取数据的请求
    23. subscriptions: {
    24. // 当监听有变化的时候就会依次执行这的变化
    25. setup({ dispatch, history }) {
    26. // 当浏览器的页面的大小变化时,触发里面的dispatch方法,save就是reducers中的方法名
    27. window.onresize = () => {
    28. dispatch({ type: "save" })
    29. }
    30. },
    31. onClick({ dispatch }) {
    32. //当鼠标点击时就会触发里面的dispatch命令,这里的save就是reducers中的方法名
    33. document.addEventListener('click', () => {
    34. dispatch({ type: "save" })
    35. })
    36. },
    37. setupHistory({history, dispatch}) {
    38. history.listen(({pathname, query}) => {
    39. if(pathname !== '/user') return;
    40. dispatch({type: 'asyncGetUser', payload: {query}});
    41. })
    42. },
    43. },
    44. }
  • subscriptions 中,只能 dispatch 当前 model 中的 reducer 和 effects

  • subscriptions 中配置的函数只会执行一次,也就是在调用 app.start() 的时候,会遍历所有 model 中的 subscriptions 执行一遍
  • subscriptions 中配置的函数需要返回一个函数,该函数应该用来取消订阅的该数据源
  • subscriptions 中配置的key的名称没有任何约束,而且只有在app.unmodel的时候才有用

subscriptions

subscription相当于一个监听器,可以监听

  • 路由变化
  • 鼠标
  • 键盘变化
  • 服务器连接变化
  • 状态变化等
  • 可以根据不同的变化做出相应的处理,根据需要dispatch相应的action

subscription格式: ({ dispatch, history }) => unsubscribe

subscriptions中的方法名是随意定的,每次变化都会一次去调用里面的所有方法,所以要加上相应的判断
subscriptions是订阅,用于订阅一个数据源

  • 可以在这里面监听路由变化,比如当路由跳转到本页面时,发起请求来获取初始数据,数据源可以是
  • 当前的时间
  • 服务器的websocket连接
  • keyboard输入
  • geolocation变化
  • history路由变化等

    1. export default {
    2. namespace: 'user',
    3. state: {
    4. data: [],
    5. },
    6. reducers: {
    7. save(state, {payload}) {
    8. return { ...state, ...payload }
    9. }
    10. },
    11. // saga里的effects,里面的各种处理异步操作的saga
    12. effects: {
    13. *asyncGetUser({ payload }, { put, call, select }) {
    14. const res = yield call(axios.post('url'), payload);
    15. yield put({ type: 'save', payload: { data: res.data } });
    16. }
    17. },
    18. subscriptions: {
    19. setupHistory({history, dispatch}) {
    20. history.listen(({pathname, query}) => {
    21. if(pathname !== '/user') return;
    22. dispatch({type: 'user/asyncGetUser', payload: {query}});
    23. // dispatch({ type: 'asyncGetUser', payload: query });
    24. })
    25. },
    26. }
    27. }

path-to-regexp

如果 subscriptions,监听的 url规则比较复杂,比如: /users/:userId/search,使用 path-to-regexp来匹配路由

  1. import pathToRegexp from 'path-to-regexp';
  2. subscriptions: {
  3. setupHistory({history, dispatch}) {
  4. history.listen(({pathname, query}) => {
  5. const match = pathToRegexp('/users/:userId/search').exec(pathname);
  6. if(match) {
  7. const userId = match[1];
  8. }
  9. dispatch({type: 'user/asyncGetUser', payload: {userId}});
  10. })
  11. },
  12. }

component

组件 dispatch(action) 触发异步更新,不需要关心数据的处理逻辑,数据处理逻辑都在 models里面
组件只关心从 props中获取对应的数据

  1. function App({dispatch}) {
  2. useEffect(() => {
  3. const action = {type: 'nampsace/saga', payload: {page: 1, limit: 10}}
  4. dispatch(action);
  5. }, [])
  6. }
  7. function mapStateToProps(state) {
  8. return {
  9. loading: state.loading.effects['user/fetch'],
  10. data: state.user.data,
  11. }
  12. }
  13. function mapDispatchToProps(dispatch){}
  14. // 依赖注入
  15. export default connect(mapDispatchToProps, null)(App);
  • 只要这个组件关心的数据没变,就不会重新渲染
  • 依赖注入:将需要的state的节点注入到与此视图数据相关的组件上