什么是Umi

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

  • 🎉 可扩展,Umi 实现了完整的生命周期,并使其插件化,Umi 内部功能也全由插件完成。此外还支持插件和插件集,以满足功能和垂直域的分层需求。
  • 📦 开箱即用,Umi 内置了路由、构建、部署、测试等,仅需一个依赖即可上手开发。并且还提供针对 React 的集成插件集,内涵丰富的功能,可满足日常 80% 的开发需求。
  • 🐠 企业级,经蚂蚁内部 3000+ 项目以及阿里、优酷、网易、飞猪、口碑等公司项目的验证,值得信赖。
  • 🚀 大量自研,包含微前端、组件打包、文档工具、请求库、hooks 库、数据流等,满足日常项目的周边需求。
  • 🌴 完备路由,同时支持配置式路由和约定式路由,同时保持功能的完备性,比如动态路由、嵌套路由、权限路由等等。
  • 🚄 面向未来,在满足需求的同时,我们也不会停止对新技术的探索。比如 dll 提速、modern mode、webpack@5、自动化 external、bundler less 等等。

官网这张图挺不错的

image-20210709101623127

开始使用

安装

# 新建文件夹
mkdir dirname && cd dirname
# 拉取初始文件
yarn create @umijs/umi-app
# 安装依赖
yanr
# 运行
yarn start

打开浏览器,访问localhost:8000端口(默认8000,如果被占用会依次往后寻找)

image-20210709102223068

目录结构

├── package.json # 包管理文件
├── .umirc.ts # umi配置文件
├── .env # 环境变量设置
├── dist	# 打包产物目录
├── mock # mock数据目录
├── public # 静态资源目录
└── src # 代码主目录
    ├── .umi # 临时文件,每次打包后会重新生成
    ├── layouts/index.tsx # 布局
    ├── pages # 页面
        ├── index.less
        └── index.tsx
    └── app.ts # 运行时配置文件

除了.umirc.ts可作为配置文件之外,可以新建.umirc.local.ts配置文件,local只有在dev环境下才会生效,并且会和.umirc.ts进行合并

app.ts是运行时配置文件,主要的作用时在运行时修改一些既有的配置

例如:使用patchRoutes来修改在路由配置中约定的路由

// 在路由配置最前面加一个foo路由
export function patchRoutes({ routes }) {
  routes.unshift({
    path: '/foo',
    exact: true,
    component: require('@/extraRoutes/foo').default,
  });
}

使用render来覆盖默认的渲染方法,可以用于权限检测等

import { history } from 'umi';

export function render(oldRender) {
  fetch('/api/auth').then(auth => {
    if (auth.isLogin) { oldRender() }
    else { 
      history.push('/login'); 
      oldRender()
    }
  });
}

使用onRouteChange可以在初始加载或路由变化时进行操作,如埋点、设置标题等

export function onRouteChange({ location, routes, action }) {
  bacon(location.pathname);
}

export function onRouteChange({ matchedRoutes }) {
  if (matchedRoutes.length) {
    document.title = matchedRoutes[matchedRoutes.length - 1].route.title || '';
  }
}

路由

现代前段开发大部分都是单页应用了,单页应用的基础就在于前端路由,页面的跳转都在浏览器完成,不会再次请求HTML,后续所有的HTML都是JS生成的

在Umi中有两种模式的路由,一种是基于约定、一种是基于配置,所谓约定就是根据事先规定好的文件位置自动生成路由文件,配置就是实现在配置文件中声明路由及对应的页面位置。

路由配置如下

const routes: IRoute[] = [
  {
    path: '/',
    exact: true,
    // redirect: '/workspace', // 跳转之后就无法命中当前声明的组件
    title: '首页',
    component: '@/pages/home/index',
    wappers: [], // 高阶路由组件
  },{
    path: '/device/detail/:name', 
    // :name,动态路由,可以在props.match.params中获取匹配的参数
    component: '@/pages/device/detail'
  },{
    path: '/user',
    redirect: '/user/list'
    routes: [
      {path: '/user/list', component: '@/pages/user/list'},
           {path: '/user/detail/:name', component: '@/pages/user/detail'},
    ]
  }
]

wappers使用方法可以看一下文档,传送门

如果没有配置路由,Umi会进入约定路由模式,根据pages目录下的文件自动生成路由,例如下面的文件目录

  └── pages
    ├── index.tsx
    └── users.tsx

就等效于这样的配置路由

[
  { exact: true, path: '/', component: '@/pages/index' },
  { exact: true, path: '/users', component: '@/pages/users' },
]

以下情况不会被注册为路由

  • ._ 开头的文件或目录
  • d.ts 结尾的类型定义文件
  • test.tsspec.tse2e.ts 结尾的测试文件(适用于 .js.jsx.tsx 文件)
  • componentscomponent 目录
  • utilsutil 目录
  • 不是 .js.jsx.ts.tsx 文件
  • 文件内容不包含 JSX 元素

使用[]包裹的文件可以被判定为动态路由,比如

  └── pages
    └── users
      └── [id].tsx
    └── index.tsx

等价于配置中的

[
  { exact: true, path: '/', component: '@/pages/index' },
  { exact: true, path: '/users/:id', component: '@/pages/users/[id]' },
]

创建_layout.tsx文件可以声明嵌套路由,例如

└── pages
    └── users
        ├── _layout.tsx
        ├── index.tsx
        └── list.tsx

会生成

[
  { exact: false, path: '/users', component: '@/pages/users/_layout',
    routes: [
      { exact: true, path: '/users', component: '@/pages/users/index' },
      { exact: true, path: '/users/list', component: '@/pages/users/list' },
    ]
  }
]

约定路由中要使用wrappers需要在组件中添加wrappers属性,例如

import React from 'react'

function User() {
  return <>user profile</>
}

User.wrappers = ['@/wrappers/auth']
// User.title = '用户中心'
// 其他路由拓展属性都可以在这里添加

export default User

页面跳转

页面跳转有声明式和命令式两种,声明式就是使用XML格式的标签来进行跳转,如

<Link to="/list">Go to list page</Link>

命令式的主要是通过history进行跳转

import { history } from 'umi';

function goToListPage() {
  // 带参跳转,两种方式等价
  history.push('/list?a=b');
    history.push({
    pathname: '/list',
    query: {
      a: 'b',
    },
  });
}
// 或者使用props中的history模块跳转
export default (props) => (
  <Button 
    onClick={()=>props.history.push('/list');}
    >Go to list page
  </Button>
);

前端小白