网络日志

umi.js常用配置学习

文章开始前我想向大家一个问题用于更好的学习Umijs,什么是Umijs?

Umi,中文可发音为乌米,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。

Umi 是蚂蚁集团的底层前端框架,已直接或间接地服务了 3000+ 应用,包括 java、node、H5 无线、离线(Hybrid)应用、纯前端 assets 应用、CMS 应用等。他已经很好地服务了我们的内部用户,同时希望他也能服务好外部用户。

以上是我摘抄自官网,说白了使用umijs可以使我们更加便捷的完成创建-开发-发布整个项目生命周期,Umijs的功能有很多,以下我将列举几个开发中常用的配置项;如果需要更加完整的配置请参考官方文档《Umijs官方文档》

开始学习,首先我们需要创建一个Umi项目

那么如何创建一个Umi项目呢?

我们需要使用命令行在控制台输入

# 国内源
$ npm i yarn tyarn -g
# 后面文档里的 yarn 换成 tyarn
$ tyarn -v

# 阿里内网源
$ tnpm i yarn @ali/yarn -g
# 后面文档里的 yarn 换成 ayarn
$ ayarn -v

以上步骤是Umijs用以安装依赖;

安装完依赖之后我们需要创建一个空目录用来承载我们的Umijs项目,在此我们可以手动创建一个空文件夹,也可以使用以下命令行创建

$ mkdir myapp && cd myapp

文件夹创建完成之后我们需要开始创建Umijs项目了,我们在命令行中输入以下命令

$ yarn create @umijs/umi-app

或者我们可以选择npx创建,如果使用npx则输入以下命令

$ npx @umijs/create-umi-app

这里需要注意的是执行创建umijs项目时,会在你当前选中目录下直接加入依赖所以必须要创建一个文件夹进行承载,不然你创建完成之后会发现创建的umijs项目并没有包裹,所以并不是你想要的答卷;

完成以上步骤之后我们需要安装umijs项目依赖,这个很简单,参考创建vue项目与react项目我们只需要执行

$ npm install
#或者
$ npm i
#或者
$ yarn install

完成以上步骤则我们的Umijs项目就构建完成了这时候我们来分析下umijs的目录结构我们以官方给出的目录结构为例子,接下来我为大家详细解读该目录结构中文件的作用

.
├── package.json
├── .umirc.ts
├── .env
├── dist
├── mock
├── public
└── src
    ├── .umi
    ├── layouts/index.tsx
    ├── pages
        ├── index.less
        └── index.tsx
    └── app.ts

根目录package.json包含插件和插件集,以 @umijs/preset-、@umijs/plugin-、umi-preset- 和 umi-plugin- 开头的依赖会被自动注册为插件或插件集。

.umirc.ts配置文件,包含 umi 内置功能和插件的配置。

.env环境变量。

比如:

PORT=8888COMPRESS=nonedist 目录执行 umi build 后,产物默认会存放在这里。

mock 目录存储 mock 文件,此目录下所有 js 和 ts 文件会被解析为 mock 文件。

public 目录此目录下所有文件会被 copy 到输出路径。

/src 目录.umi 目录临时文件目录,比如入口文件、路由等,都会被临时生成到这里。不要提交 .umi 目录到 git 仓库,他们会在 umi dev 和 umi build 时被删除并重新生成。

layouts/index.tsx约定式路由时的全局布局文件。

pages 目录所有路由组件存放在这里。

app.ts运行时配置文件,可以在这里扩展运行时的能力,比如修改路由、修改 render 方法等。

以上摘抄自《Umijs官方文档》

如果你成功构建项目你会发现目录中还有以下文件这些文件大多是一些项目代码风格的配置项,因为与项目进程没有太大关系,我简单解读一下

.editorconfig //编辑器代码风格配置,如首行缩进,编码方式等等;
.gitignore//git提交忽略项,在该文件中你可以设置忽略提交的文件;
.prettierignore//格式化代码时忽略的文件;
.prettierrc//格式化代码配置文件相比较于.editorconfig该文件作用于文件,而.editorconfig作用于代码
tsconfig.json//ts配置文件
typings.d.ts//ts类型定义文件

以上就是我对umijs项目目录的解读,当然还有在目录中没有涉及到的文件比如document.ejs;接下来我将为你详细解读;

当我们熟悉Umijs的整个目录结构,我们就要开始学习umijs在项目中的常用配置项,以及一些重要知识点;我将分为以下几个模块来进行解读:

  1. 常用配置 .umirc.ts;
  2. 承载文件与入口文件document.ejs;
  3. Umi Router与React Router的差异;
  4. 模拟数据 mock的基础使用;
  5. 运行时配置 app.ts;
常用配置 .umirc.ts

项目创建之后你会发现目录中有一个名为.umirc.ts的ts文件,该文件为umijs的主要配置文件,包含 umi 内置功能和插件的配置;

打开文件后你会发现一些你可能熟悉的配置

大致选项如下:

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/', component: '@/pages/index' },
  ]
  fastRefresh: {},
});

但是工作中我们可能要用到更多配置因此我们会在该文件中配置更多选项,以下我将介绍.umirc.ts的一些常用配置选项,以下会部分借鉴《Umijs常用配置》一文(因为我懒)

// config/config.js示例
export default {
  base: '/web/',  //部署到非根目录时才需配置
  publicPath: '/web/', //部署到非根目录和base一起使用
  mock:true,//是否开启mock
  targets: { //配置浏览器最低版本,比如兼容ie11
   ie: 11
  },
  history:{type:'hash'},//配置路由history模式
  hash: true,  //开启打包文件的hash值后缀
  treeShaking: true, //去除那些引用的但却没有使用的代码
  plugins: [],//umijs插件配置项
  //配置式路由时,路由文件由此引用(往下会讲到)
  routes: routes,
  //为解决跨域问题的反向代理以下为将http://xxx/代理为/api
  proxy: {
    "/api": {
      "target": "http://xxx/",
      "changeOrigin": true,
      "pathRewrite": { "^/api" : "" }
    }
  },
  alias: {'@': resolve(__dirname, '../src'),} //别名,umirc.js为'src'
};

以上为我认为一些常用的umijs配置项,如有遗漏欢迎补充;

承载文件与入口文件document.ejs

如果你在创建项目之后仔细的与react项目与vue项目做比对,你会发现你找不到index.html这个入口文件,请不要觉得奇怪,它只是用另一种形式展现给你,而你在scr目录下是看不到它的,那么它去了哪里呢?它存在与你的node_modules下的@umijs\core\lib\Html,打开Html文件夹你会发现下面有一个document.ejs文件,而当你打开document.ejs文件你会发现这个文件它里面其实是一段Html代码;代码如下:

<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
</head>
<body>

<div id="<%= context.config.mountElementId || 'root' %>"></div>

</body>
</html>

入口文件index.html现在就是以document.ejs的形式存在于你的node_modules中,那么在此我们就要有疑问了,如果我们需要在入口文件中引入一些插件,比如高德地图,或者微信JSSDK,钉钉JSSDK等要怎么办呢?别着急,你可以在你的src/pages目录下创建一个document.ejs文件,并复制粘贴node_modules\@umijs\core\lib\Html\document.ejs,里的代码进行改写;这样子你就拥有了一个你想要的入口文件;当然如果你在它的html标签中进行文档流书写,则可以自定义你的模板文件,例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>自定义模板文件</title>
</head>
<body>
    <div>哈哈哈哈哈哈</div>
</body>
</html>

如果你这么做了,那么div中的【哈哈哈哈哈哈哈哈】会一直渲染在你的页面当中;

路由 Router

UmiRouter是基于ReactRouter开发的,两者之间存在少量差异,如果你熟悉vue那么你能很快上手该知识点

umijs使用的是约定式路由,这点与vue类似,你如果你看了以上的.umirc.ts你会发现其中有一个routes配置项,umi的路由协商配置就是在这里完成的;

配置路由在配置文件中通过 routes 进行配置,格式为路由信息的数组。

比如:

export default {
  routes: [
    { exact: true, path: '/', component: 'index' },
    { exact: true, path: '/user', component: 'user' },
  ],
}

pathType: string配置可以被 path-to-regexp@^1.7.0 理解的路径通配符。

componentType: string配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。

如果指向 src 目录的文件,可以用 @,也可以用 ../。比如 component: '@/layouts/basic',或者 component: '../layouts/basic',推荐用前者。

exactType: booleanDefault: true表示是否严格匹配,即 location 是否和 path 完全对应上。

比如:

export default {
  routes: [
    // url 为 /one/two 时匹配失败
    { path: '/one', exact: true },
    
    // url 为 /one/two 时匹配成功
    { path: '/one' },
    { path: '/one', exact: false },
  ],
}

routes配置子路由,通常在需要为多个路径增加 layout 组件时使用。

比如:

export default {
  routes: [
    { path: '/login', component: 'login' },
    {
      path: '/',
      component: '@/layouts/index',
      routes: [
        { path: '/list', component: 'list' },
        { path: '/admin', component: 'admin' },
      ],
    }, 
  ],
}

然后在 src/layouts/index 中通过 props.children 渲染子路由,

export default (props) => {
  return <div style={{ padding: 20 }}>{ props.children }</div>;
}

这样,访问 /list 和 /admin 就会带上 src/layouts/index 这个 layout 组件。

redirectType: string配置路由跳转。

比如:

export default {
  routes: [
    { exact: true, path: '/', redirect: '/list' },
    { exact: true, path: '/list', component: 'list' },
  ],
}

访问 / 会跳转到 /list,并由 src/pages/list 文件进行渲染。

wrappersType: string[]配置路由的高阶组件封装。

比如,可以用于路由级别的权限校验:

export default {
  routes: [
    { path: '/user', component: 'user',
      wrappers: [
        '@/wrappers/auth',
      ],
    },
    { path: '/login', component: 'login' },
  ]
}

然后在 src/wrappers/auth 中,

import { Redirect } from 'umi'
引用
export default (props) => {
  const { isLogin } = useAuth();
  if (isLogin) {
    return <div>{ props.children }</div>;
  } else {
    return <Redirect to="/login" />;
  }
}

这样,访问 /user,就通过 useAuth 做权限校验,如果通过,渲染 src/pages/user,否则跳转到 /login,由 src/pages/login 进行渲染。

titleType: string配置路由的标题。

页面跳转

import { history } from 'umi';

// 跳转到指定路由
history.push('/list');

// 带参数跳转到指定路由
history.push('/list?a=b');
history.push({
  pathname: '/list',
  query: {
    a: 'b',
  },
});

// 跳转到上一个路由
history.goBack();

hash 路由详见 配置#history。

Link 组件比如:

import { Link } from 'umi';

export default () => (
  <div>
    <Link to="/users">Users Page</Link>
  </div>
);

然后点击 Users Page 就会跳转到 /users 地址。

注意:

Link 只用于单页应用的内部跳转,如果是外部地址跳转请使用 a 标签路由组件参数路由组件可通过 props 获取到以下属性,

match,当前路由和 url match 后的对象,包含 params、path、url 和 isExact 属性location,表示应用当前处于哪个位置,包含 pathname、search、query 等属性history,同 api#history 接口route,当前路由配置,包含 path、exact、component、routes 等routes,全部路由信息比如:

export default function(props) {
  console.log(props.route);
  return <div>Home Page</div>;
}

传递参数给子路由通过 cloneElement,一次就好(Umi 2 时需要两次)。

import React from 'react';

export default function Layout(props) {
  return React.Children.map(props.children, child => {
    return React.cloneElement(child, { foo: 'bar' });
  });
}

路由模块均为重要知识点所以以上路由模块我均借用文档《Umijs官方文档》

模拟数据 mock的基础使用

在工作中,我们常常遇到后端接口提供不及时导致影响开发进度,因此我们时常需要创建一些假数据来模拟提前开发,因此引入了mock的概念,umijs很好的融合了mock,那么我们要怎样进行mock开发呢?首先我们需要在.umirc.ts中打开mock配置,我们只需将mock配置设成true即可,该配置默认为false;mock:true当我们开启了mock设置,那么我们要怎么样来书写mock接口呢?也很简单;我们在主目录中找到mock文件夹,在下面创建一个index.ts文件;umi会自动引用该ts文件为mock入口下面我简单的为大家书写几个mock接口当作案例;

export default {
  'GET /api/user':{
    status:'success',
    message:'请求成功',
    data:{
      name:'王大锤',
      sex:1,
      age:18,
    }
  },

  'PUT /api/user':{
    status:'success',
    message:'更新成功',
    data:{

    }
  },

  'DELETE /api/money':{
    status:'success',
    message:'删除成功',
    data:{

    }
  },

  'POST /api/money':{
    status:'success',
    message:'删除成功',
    data:{

    }
  }
}

接口写好了,那我们要怎样使用呢?也很简单,这里方法有很多,我就选用axios来进行请求首先我们当然要先安装axios以下为安装命令

npm install axios
或者
npm i axios
或者
yarn add axios

安装完之后我们在项目中引用

因为只做引用所以在此我不进行axios的封装,详细封装教程我在之后会讲

import axios from 'axios'

export default function test(){
    axios.get('/api/user').then(res=>{
    /**res = {status:'success',
    message:'请求成功',
    data:{
      name:'王大锤',
      sex:1,
      age:18,
    }}**/
})
}

至此我们就大致讲完了umijs的mock数据流程

运行时配置 app.ts

运行时配置需要在app.ts当中添加使用,但是你可能发现你刚刚创建的项目当中没有app.ts文件,那么就需要我们手动添加了;

首先我们需要在src目录下添加一个app.ts文件,然后我们需要在里面重写几个方法;

运行时配置主要的配置方法有五个分别是:

  1. patchRoutes({ routes });
  2. render(oldRender: Function);
  3. onRouteChange({ routes, matchedRoutes, location, action });
  4. rootContainer(LastRootContainer, args);
  5. modifyClientRenderOpts(fn);

我将按顺序一一介绍以上五个方法;

patchRoutes({ routes })用于修改路由该路由不是当前路由而是你的整个约定式路由配置;也就是你在.umirc.ts种routes里的配置

当你启动该项目时该方法将会被调用,并且传入一个{routes}参数该参数的内容就是你的routes配置;

由于routes参数是一个数组,所以你可以用一切可以改变数组内容的方法来修改你的路由配置例如unshift,push等等

由于该方法没有返回值,所以你需要在方法内部直接操作routes参数才能修改你的路由配置;

export function patchRoutes({ routes }) {
  routes.unshift({
    path: '/foo',
    exact: true,
    component: require('@/extraRoutes/foo').default,
  });
}

如果你选择了添加一个新路由到你的路由配置项当中需要注意的是该路由的component必须是项目中存在的,否则将会报错;在项目中你可能用到该方法的场景有权限的校验返回不同的路由配置等等;

render(oldRender: Function)覆写 render。

当调用该方法时会传入一个oldRender参数;该方法时全局性的,所以当你启动项目时便会被调用

如果你不运行oldRender方法责将阻止渲染你将得到一个空白页面;

export function render(oldRender) {
  oldRender()
  });
}

所以你可以在页面加载前判断一些必要加载条件,比如权限校验;参数传入等等;由于该方法在patchRoutes之前运行你可以配合patchRoutes打出一套组合拳;比如在render当中判断权限,然后在patchRoutes当中进行路由配置初始化;例如官方案例

let extraRoutes;

export function patchRoutes({ routes }) {
  merge(routes, extraRoutes);
}

export function render(oldRender) {
  fetch('/api/routes').then(res=>res.json()).then((res) => { 
    extraRoutes = res.routes;
    oldRender();
  })
}

onRouteChange({ routes, matchedRoutes, location, action })只要路由切换或者加载该方法都会被执行类似于vue router当中的routerEach;你可以在路由切换的同时记录埋点,由于该方法返回四个参数包括详细的路由参数,你可以在此判断是否执行向下操作

export function onRouteChange({ routes,matchedRoutes,location, action }) {
        console.log(routes);  路由集合
        console.log(matchedRoutes);  当前匹配的路由及其子路由
        console.log(location);   location及其参数
        console.log(action);   当前跳转执行的操作  
      }

rootContainer(LastRootContainer, args)

修改交给 react-dom 渲染时的根组件。

由于该方法使用场景较少在此我直接引用官方案例

//修改react-dom渲染时的根组件并在外面包一个Provider
export function rootContainer(container) {
  return React.createElement(ThemeProvider, null, container);
}

modifyClientRenderOpts(fn)

修改 clientRender 参数该方法在一般项目中极少用到,我唯一能想到的也就是官方文档中给出的在修改微前端节点,具体还需要根据使用场景来讲,所以我会简单给出一个案例,余下的我会在之后的微前端章节当中演示;

let isSubApp = false;
export function modifyClientRenderOpts(memo) {
  return {
    ...memo,
    rootElement: isSubApp ? 'sub-root' : memo.rootElement,    
  };
}
其他

讲点其他的,Umijs是阿里开发的企业级框架,所以该框架中自带阿里引以为傲的antd你可以在umijs项目中直接引用antd的组件而不需要npm安装;Umijs给我最大的感受就是让我想起了当年写vue2的日子,umijs做了很多整合,简化了很多操作,提供了强大的插件;但是我却并不是特别喜欢该框架,感觉使用umijs你将会少了很多探索,因此你可能失去很多知识点,如果你没用过react第一个项目就是umijs项目我并不建议你直接使用,umijs改变了很多react的语法,虽然便捷了但是并不能让你很好掌握react;当然如果你想要便捷迅速的来进行开发大可使用umijs。