dva 中进行页面复用实践总结

目录:
一、要做的事
二、思路
1. 路由改变 pathname —— 菜单数组
2. dva 监听 pathname 变化,并显示与当前 pathname 对应的页面 —— 路由数组
3. 导出菜单数组和路由数组
三、踩坑
1. 引入 npm 包运行报错
2. npm 包进行页面复用注意事项
3. npm 包样式问题

本文针对有 dva 开发经验的阅读,没实际开发过可能看着有点晕。

一、要做的事

最近经过尝试,发现页面也可以进行复用。如下是最最普通的后台管理系统,左边的一个菜单对应右边的一个页面,该页面内有基本的增删改查功能。现在可以将这样一个页面发布成 npm 包,在其他系统如果也需要这个页面,直接 install 对应的包通过简单配置即可。

图1:普通后管系统

我有这个想法源自我开发过好几个后管系统,每个后管系统都有基础的权限管理功能,也就是 用户管理角色管理机构管理。每次我都需要重新开发,哪怕是复制粘贴代码也显示很麻烦。要是其中一个后管系统的权限管理功能有一个地方需要修改,还要去其它后管系统一个个修改。如果将权限管理功能涉及的代码打成 npm 包,每次只需要修改 npm 包对应的那部分代码,之后发布新的版本,用到权限管理菜单的后管系统只需要简单的更新到最新版本即可,这显得很人性化。

dva 可以很方便的做到这一点,下面就介绍 dva 中页面的复用步骤以及注意事项。

二、思路

1. 路由改变 pathname —— 菜单数组

通过路由改变浏览器地址栏中 pathname。

dva 中使用的路由是 react-router@4.0,其中有个标签是这样的:<Link to="/user">,它的目的是将浏览器地址栏中 pathname 替换成 /user

图2:菜单树

对于菜单树,每个菜单项都有名称(name),有的菜单项还有图标(icon),菜单有父子关系(parentId),如果菜单是父级菜单,是没有路由的,点击父级菜单只会打开折叠显示所有子菜单,如果菜单是子菜单,拥有路由属性(router),点击菜单会改变浏览器地址栏中的 pathname 值。

let menus = [
  {
    id: '1',
    name: 'List Page',
    parentId: null,
  },
  {
    id: '11',
    name: 'Table List',
    router: '/table-list',
    parentId: '1',
  },
];

通过组装如上的数据结构生成菜单树的 html 代码。如果需要添加新的菜单,只需要追加一个数组即可。数据源变化,左边菜单树会自动新增一项菜单。

// ......
const newMenu = [{
    id: '2',
    name: 'Form Page',
    parentId: null,
}]
menus  = menus.concat(newMenu);

2. dva 监听 pathname 变化并显示对应页面 —— 路由数组

(1)routes 目录和 models 目录

使用 dva-cli 创建的项目有三个很特别的目录:modelsroutesservices

其中 routes 目录下的内容是用 react jsx 语法写的 页面。也就是图1中点击菜单后右边显示的页面。

models 目录提供数据,如右面页面中的表格内容是一个数组,就是由 models 提供给 routes 的,然后 routes 中把这些数据转化为表格。一个 models 对应一个 routes。

(2)dva 监听 pathname 变化并显示对应页面

dva 提供一个 API 叫做注册路由表 app.router(({ history, app }) => RouterConfig)

import { Router, Route } from 'dva/router';
// 引入 routes 目录里的“页面”
import TableList from './routes/table-list/index.js'
app.router(({ history }) => {
  return (
    <Router history={history}>
      <Route path="/table-list" component={TableList } />
    <Router>
  );
});

上述代码意思是当 pathname 变成 /table-list 时,就显示 routes 目录下的 TableList 页面。(还记得 pathname 在哪里改变吗?上面路由中有介绍)这里涉及两个东东:path页面,如果页面需要数据,事实上还需要一个 models

实际编码中可以对上述代码稍作修改:

import React from 'react';
import dynamic from 'dva/dynamic';
import { routerRedux, Route, Switch, Redirect } from 'dva/router';

const { ConnectedRouter } = routerRedux;

function RouterConfig({ history, app }) {
  const error = dynamic({
    app,
    component: () => import('./routes/system/error/index'),
  });
   
  // >>>>>>>>>>>>>>>路由数组 START <<<<<<<<<<<<<<<
  let routes = [
    {
      path: '/table-list',
      models: () => [import('./models/table-list.index.js')],
      component: () => import('./routes/table-list.index.js'),
    },
  ];
  // >>>>>>>>>>>>>>>路由数组 END <<<<<<<<<<<<<<<

  // 将数据源转换为 <Router > 标签的形式........
  return (
    <ConnectedRouter history={history}>
      <Page>
        <Switch>
          <Route path="/" exact render={() => (<Redirect to="/dashboard" />)} />
          {
            routes.map(({ path, ...dynamics }, key) => (
              <Route
                exact
                key={key}
                path={path}
                component={dynamic({ app, ...dynamics })}
              />
            ))
          }
          <Route component={error} />
        </Switch>
      </Page>
    </ConnectedRouter>
  );
}

export default RouterConfig;

这里将 path页面 也抽离为数组作为数据源,在通过处理转换为 dva 提供的 API。

上述代码中,已经成功将 path(也就是 pathname) 与 routes 目录下的页面和 models 目录下提供数据的容器绑定到了一起。此时通过 <Link to="/table-list">将浏览器地址栏 pathname 变换成 /table-list 时就可以显示对应页面。

3. 导出菜单数组和路由数组

经过上面的分析,一个菜单涉及两部分内容:菜单数组决定如何改变 pathname,路由数组决定监听到 pathname 变化时显示什么页面。

只需要将这两个数组导出,在其它后管系统中追加改变数据源即可实现页面的复用。

在 src 目录下新建文件 export.js

const menus = [
   {
    id: '2',
    name: 'Form Page',
    parentId: null,
   }
];

// 注意这里的路径使用相对路径
const routes = [
   {
      path: '/table-list',
      models: () => [import('./models/table-list.index.js')],
      component: () => import('./routes/table-list.index.js'),
    },
];

const baseObj = {
  baseMenus: menus,
  baseRoutes: routes
}

export default baseObj ;

修改 package.json 设置入口文件位置:

{
  "name": "test-user",
  "main": "src/export.js"
}

接着就可以发布 npm 包了。对发布过程不了解的可以跳转 npm 发布包流程及填坑指南

在其它后管系统中引用也很简单,将路由数组和菜单数组追加到相应位置即可。

三、踩坑

1. 引入 npm 包运行报错

引入 npm 包之后运行会报如下错误。原因是 node_modules 目录下的 npm 包里用到了 es6 的语法,虽然 roadhog 能自动将 es6 代码处理为 es5 ,但这并不包括 node_modules 目录下的代码。

You may need an appropriate loader to handle this file type.
| import React from 'react';
| /*
| import { render } from 'react-dom'

解决方法: 修改 .roadhogrc.js 文件,添加 extraBabelIncludes 属性,其中 test-user 为你发布的 npm 包名称。该属性意思是让 roadhog 将 node_modules/test-user 代码中的 es6 处理为 es5,这样就可以正常启动运行了。

export default {
  "entry": "src/index.js",
  "extraBabelIncludes": [
    "node_modules/test-user"
  ],
  // ..........
}

2. npm 包进行页面复用注意事项

  • 所有后管系统前端框架要一样。npm 包将业务模块的代码抽出,这样其实就是将代码位置挪到 npm 包里,然后在当前后管系统中添加一个路径引用而已;
  • 测试发现,外层后管系统使用 roadhog 可以配置一些路径别名,npm 包里的代码是可以调用到外层的这些别名的,这意味着 npm 包里的 npm 可以与外层系统公用一份配置文件;
  • npm 包里的 Mock 数据没法直接使用。这是因为 roadhog 在启动项目时会自动调用 .roadhogrc.mock.js 文件中的 mock 文件,除非你在该文件中引入 npm 包里的 mock 文件。

3. npm 包样式问题

(2018年3月9日更新)最近发现一个比较恶心的问题,打好的 npm 包在其它后管项目中引用,npm 包里如果样式是这么写的。在 index.js 中引入 index.less,会发现 index.less 中的样式不起作用;要是把样式直接在 index.js 以内联的方式 style = {{ }} 发现是可以的。推测原因,外部引入 npm 包,不会执行 .less 文件。

- routes
  - user
    - index.js
    - index.less
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,598评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • 上一章 下一章 在客厅等了一会儿,见霍安泽还不出来,阿眉果断上前敲门:“霍先生,你好了没啊?” 门打开,霍安泽比了...
    李小胖的妈妈阅读 984评论 24 30
  • 凡事贵在坚持,我不想再给自己偷懒的的理由。写作不是1天2天就能改变一个人的思维与逻辑,而在于不断的学习。...
    lyxu阅读 430评论 0 1
  • 影片中的老虎一方面代表了派的恐惧,另一方面代表人的兽性与恶的一面。 恐惧和兽性,让少年免于海上死亡的命运...
    齐天大圣_YangDQ阅读 257评论 1 3