sorrycc / blog

💡

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

使用 umi 改进 dva 项目开发

sorrycc opened this issue · comments

最近给 antd-admin 提了迁移到 umi 的 PR,过程中发现使用 umi + umi-plugin-dva 的方式在代码组织、可维护性等方面相比之前都有不少提升,在这里介绍给大家。

Duck Directory

大家都说命名是编程中最难的事情之一,我觉得目录结构组织也差不了多少。

Duck Directory 可以看下图理解下,是指按功能/页面维护进行目录组织,与之相对的是一种扁平的目录组织形式。

dva 项目之前通常都是这种扁平的组织方式,

+ models
  - global.js
  - a1.js
  - a2.js
  - b.js
+ services
  - a.js
  - b.js
+ routes
  - PageA.js
  - PageB.js

用了 umi 后,可以按页面维度进行组织,

+ models/global.js
+ pages
  + a
    - index.js
    + models
      - a1.js
      - a2.js
    + services
      - a.js
  + b
    - index.js
    - model.js
    - service.js

好处是更加结构更加清晰了,减少耦合,一删全删,方便 copy 和共享。

自动注册 models

详见 umijs/umi#171

+ src
  + models
    - g.js
  + pages
    + a
      + models
        - a.js
        - b.js
        + ss
          - s.js
      - page.js
    + c
      - model.js
      + d
        + models
          - d.js
        - page.js
      - page.js

如上目录:

  • global model 为 src/models/g.js
  • /a 的 page model 为 src/pages/a/models/{a,b,ss/s}.js
  • /c 的 page model 为 src/pages/c/model.js
  • /c/d 的 page model 为 src/pages/c/model.js, src/pages/c/d/models/d.js

再见!router.js

改造前,

改造后,

再见!query-string

umi 内置的 history 是处理了 location.query 的,所以大家可以回到 dva@1 的时代,无需手动同 query-string 进行编码和解码了。

再见!配置文件

首先,我们的 package.json 里会少很多依赖,

  • dva-loading
  • dva-hmr
  • dva
  • react
  • react-dom

如果你用了 antd,那么还可以省掉

  • antd
  • antd-mobile
  • babel-plugin-import

然后,webpack.config.js、原 .roadhogrc.js、原 .roadhogrc.mock.js 也能大幅省略,参考 zuiidea/antd-admin@59e9a10

FAQ

如何配置 onError、initialState 等 hook?

新建 src/dva.js,通过导出的 config 方法来返回额外配置项,比如:

import { message } from 'antd';

export function config() {
  return {
    onError(err) {
      err.preventDefault();
      message.error(err.message);
    },
    initialState: {
      global: {
        text: 'hi umi + dva',
      },
    },
  };
}

url 变化了,但页面组件也刷新,是什么原因?

layouts/index.js 里如果用了 connect 传数据,需要用 umi/withRouter 高阶一下。

import withRouter from 'umi/withRouter';

export default withRouter(connect(mapStateToProps)(LayoutComponent));

如何访问到 store 或 dispatch 方法?

window.g_app._store
window.g_app._store.dispatch

如何禁用包括 component 和 models 的按需加载?

在 .umirc.js 里配置:

export default {
  disableDynamicImport: true,
};

umi 社区

钉钉群

微信群

扫码加 UMI_HELPER,回复 umi 自动加群。

参考

commented

@sorrycc

url 变化了,但页面组件也刷新,是什么原因?

应该是页面不刷新

umi 内置的 history 是处理了 location.query 的,所以大家可以回到 dva@1

dva@1 是个啥用法

@superlbr 就是正常使用,通过location.query获取到查询字符对象,而dva2.0中因为history升级的原因,取而代之的是location.search去获取查询字符串,umi内置了这个 解析字符串的功能

明白,location.query 不需要queryString.parse(location.search)了,但是queryString.stringify貌似还是要用下。。。

@sorrycc antd依赖由umi维护,那依赖升级怎么操作呢,是否有类似egg.js的autod?

antd依赖由umi维护,那依赖升级怎么操作呢,是否有类似egg.js的autod?

@superlbr 项目里有 antd 依赖会优先用项目里的。

新建 src/dva.js,通过导出的 config 方法来返回额外配置项
可以在具体一些吗?加入了没有反应

@sl5310 能具体描述一下你的问题,或者给一个不可行的例子吗?

@xiaohuoni 我在src目录下加了dva.js

import { message } from 'antd';

export function config() {
	return {
		onError(err) {
			err.preventDefault();
			message.error(err.message);
		},
	};
}

也重新跑了npm start 是否还需要加入其他东西呢?
在生成DvaContainer.js 并没有看见onError被加入,
我也尝试在src/plugin加入onError.js 加入以上的code,也没有看见plugin自动载入去DvaContainer.js

@sorrycc 谦神,能不能给antd-design-pro也提个pr,把umi搞进来

@sorrycc 谦神,能不能给antd-design-pro也提个pr,把umi搞进来

commented

umijs的目录组织结构很不错,和大神想到一块了

我觉得以页面为划分维度是不合适的,因为同一块数据可能会在不同的页面上都展示,那这块数据的业务逻辑要写两套吗?列表和对应详情是两个页面,但显然它们应该属于一块service和model。数据和页面之间不是一一对应的,一个页面可能有好几块数据,一块数据也可能分散到好多个页面,以页面为维度会带来冗余和复杂。

@onecompany
service和model可以share,直接require。可以public,放services文件夹
services文件夹不一定要跟pages对应呀,可以自己梳理逻辑。
更不必说redux里可以操作不同model的数据

@superlbr
按你方案碰到“一个页面可能有好几块数据,一块数据也可能分散到好多个页面”这种情况,它们之间的调用会非常复杂的。

模块化的目的是每个模块是独立的,很少要调用其他模块。

模块的划分应该以业务和数据域为依据,页面只是ui而已,它后期的变化可能会很大,以它为维度是难以维护的。

@onecompany

一个页面可能有好几块数据

可以把一个页面当成一个模块去处理,pages里service可以放好几块调用的声明

一块数据也可能分散到好多个页面

可以public,services下建文件夹或者放global.js

它后期的变化可能会很大,以它为维度是难以维护的。

页面按模块组织,页面增减直接增删文件夹,public的部分视情况增减就ok

总体来说,考虑到了灵活性和颗粒度

@superlbr

可以把一个页面当成一个模块去处理,pages里service可以放好几块调用的声明

好,比如这个页面含5块数据;如果又有一个页面含这个5个中的3个,又有一个模块含这5个中的4个, 并且它们的数据字段详细程度还不一样(详情级别和快照级别)..., 于是很多页面都要调用这个页面下的service,并且这个service还要不断的维护,这就太混乱了!

可以public,services下建文件夹或者放global.js

如果这样的数据比较多,那这个global无疑会变大很大,很难维护。

再加上如果后期页面的展示内容要大改呢!

@onecompany

比如这个页面含5块数据;如果又有一个页面含这个5个中的3个...

那可以认为这几个页面是相关的,放在同一个文件夹下,即pages/[A/B/C/services/models],
ABC按需求require service/model

数据字段详细程度还不一样(详情级别和快照级别)

控制传参

如果这样的数据比较多,那这个global无疑会变大很大,很难维护

做好注释和说明,应该还好把

如果后期页面的展示内容要大改呢

没有银弹吧。。。数据流改改能用吗

这种不可以简单的认为是相关的,比如这个页面含5块数据;如果又有一个页面含这个5个中的3个,但它同时又含有另外的7块数据呢,它要和谁相关??这个两个页面本来是平行的,你不能写成嵌套的。

不能简单的控制传参,因为后端可能不是通过传参,而是给两个不同的端点。

如果以数据为维度是不会有这些问题的。因为它根本不考虑页面,完全可以在不写页面的情况下,写完整个业务逻辑。

如果以数据为维度...

请问数据怎么解决这些问题?跟现有model/service有什么差异吗?
不考虑页面?那页面怎么组织?

+artilces
-sevices
-models
+components
-Item.js
-List.js

service和modles包含了article这块数据的所有增删改查操作,Item和List是纯ui组件,只接受数据。

所谓的页面,就是把它们组合起来就行了,dva基本是这个思路,你可以看看它的hacknews项目。

@onecompany

看不出任何差别,umi + dva, 就是这样的呀

我articles不是pages里面的,它是一个独立的模块,和具体的页面无关。

@onecompany
那跟services下面建articles, models下面建articles 一样呗。umi的pages可以放模块呀,配置式路由。
既然是独立模块,那得指定路由/指定渲染,而且各种require UI组件,可维护性高?遇到删功能、删模块、删组件,一发动全身

模块化是一个大的主题,你可以看看相关文章。

@onecompany 脱离业务谈模块有点皮啦。考虑到umi毕竟是一个开源框架,得通用呀

@onecompany
@superlbr
https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/tutorial/03-%E8%AE%BE%E8%AE%A1Model.md

看两位大佬讨论受益匪浅,dva的文档有模块组织的建议,但是讨论的场景比较简单。模块化确实是一个大主题,@sorrycc 不知道谦谦有没有什么好的文章或者建议给到我们

@onecompany why not angular

想问一下 根据目录结构生成route.js 的话 如何处理当url不同却指向同一个component的问题。我现在遇到了这么一个情况,两个route之间差别不大,按照原来的方法,我可以配置不同路径对应相同component,但是现在的话应该这么做?

请问page js 访问page model要怎样实现呢?如果还是在页面里实现mapStateToProps然后connect,那跟原来一样啊,而且访问都是所有的state数据。
另外,文档中提到的“页面 model 不能被其他页面所引用”到底是一种建议的约定还是实际的技术限制呢?

@gluefxu 页面model是按需加载的,访问的时候,所有的state并不一定是所有的。至于另一个问题,会有重复引用的问题

@xiaohuoni 我现在有2个页面model,一个叫dashboard,一个叫product,如果dashboard的model已经加载了,我在product page的时候connect起dashboard model,一样可以获取dashboard的state,这是正常的吗?所以文档中提到的“页面 model 不能被其他页面所引用”是一种建议的约定吗?

commented

请问目录约定的路由形式如何配置browserHistory的base路径?

@sl5310 我一样的,你这么用,要是页面加载顺序改变的话,这个就会出错了。

commented

用umi脚手架创建项目,插件也配置了,connect后,dispatch正常,但是报put,call不是function的错,怎么回事?

大神有做过umi dva single-spa 微前端的例子吗

commented

请问model里面的state什么时候回重置到初始值,单页应用中,A页面中state初始值是0,dispatch为1,后跳转B页面,再回A页面,state值不会重置?

请问model里面的state什么时候回重置到初始值,单页应用中,A页面中state初始值是0,dispatch为1,后跳转B页面,再回A页面,state值不会重置?

不会重置,你需要自己重置

commented

nice

请问model里面的state什么时候回重置到初始值,单页应用中,A页面中state初始值是0,dispatch为1,后跳转B页面,再回A页面,state值不会重置?

如果不需要保存A页面的数据,可以存在state里

commented

想问一下 根据目录结构生成route.js 的话 如何处理当url不同却指向同一个component的问题。我现在遇到了这么一个情况,两个route之间差别不大,按照原来的方法,我可以配置不同路径对应相同component,但是现在的话应该这么做?

我也在想这个问题,之前项目就是通过不同url 指向同一个component解决,如果根据目录结构生成,现在还没有想到方法。
@OPY-bbt 这种情况下你解决了吗? 可以告知下解决方案?