UmiJS上手

文章目录

UmiJS上手

一、安装环境

首先得有 node,并确保 node 版本是 10.13 或以上。

使用 yarn 管理 npm 依赖

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

安装umi.js(建议使用cnpm安装,使用yarn安装配置会很麻烦)

cnpm install -g umi
cnpm install -g create-umi

安装启动项目

create-umi

二、目录结构

2.1 目录结构

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

2.2 根目录

2.2.1 package.json

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

2.2.2 .umirc.ts

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

2.2.3 .env

环境变量。

PORT=8888
COMPRESS=none
2.2.4 dist 目录

执行 umi build 后,产物默认会存放在这里。

2.2.5 mock 目录

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

2.2.6 public 目录

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

2.3 /src 目录

2.3.1 .umi 目录

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

2.3.2 layouts/index.tsx

约定式路由时的全局布局文件。

2.3.3 pages 目录

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

2.3.4 app.ts

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

三、配置

Umi 在 .umirc.tsconfig/config.ts 中配置项目和插件,支持 es6。一份常见的配置如下,

export default {
  base: '/docs/',
  publicPath: '/static/',
  hash: true,
  history: {
    type: 'hash',
  },
}

3.1 配置文件

如果项目的配置不复杂,推荐在 .umirc.ts 中写配置; 如果项目的配置比较复杂,可以将配置写在 config/config.ts 中,并把配置的一部分拆分出去,比如路由配置可以拆分成单独的 routes.ts

// config/routes.ts
export default [
    { exact: true, path: '/', component: 'index' },
];
// config/config.ts
import { defineConfig } from 'umi';
import routes from './routes';
export default defineConfig({
  routes: routes,
});

推荐两种配置方式二选一,.umirc.ts 优先级更高。

3.2 TypeScript 提示

如果你想在写配置时也有提示,可以通过 umi 的 defineConfig 方法定义配置,

import { defineConfig } from 'umi';
export default defineConfig({
  routes: [
    { path: '/', component: '@/pages/index' },
  ],
});

在这里插入图片描述

3.3 本地临时配置

可以新建 .umirc.local.ts,这份配置会和 .umirc.ts 做 deep merge 后形成最终配置。

注:`.umirc.local.ts` 仅在 umi dev 时有效。umi build 时不会被加载

比如,

// .umirc.ts 或者 config/config.ts
export default { a: 1, b: 2 };
// .umirc.local.ts 或者 config/config.local.ts
export default { c: 'local' };

拿到的配置是:

{
  a: 1,
  b: 2,
  c: 'local',
}

注意:

  • config/config.ts 对应的是 config/config.local.ts
  • .local.ts 是本地验证使用的临时配置,请将其添加到 .gitignore,务必不要提交到 git 仓库中
  • .local.ts 配置的优先级最高,比 UMI_ENV 指定的配置更高

3.4 多环境多份配置

可以通过环境变量 UMI_ENV 区分不同环境来指定配置。

举个例子,

// .umirc.js 或者 config/config.js
export default { a: 1, b: 2 };
// .umirc.cloud.js 或者 config/config.cloud.js
export default { b: 'cloud', c: 'cloud' };
// .umirc.local.js 或者 config/config.local.js
export default { c: 'local' };

不指定 UMI_ENV 时,拿到的配置是:

{
  a: 1,
  b: 2,
  c: 'local',
}

指定 UMI_ENV=cloud 时,拿到的配置是:

{
  a: 1,
  b: 'cloud',
  c: 'cloud',
}

3.5 运行时配置

运行时配置和配置的区别是他跑在浏览器端,基于此,我们可以在这里写函数、jsx、import 浏览器端依赖等等,注意不要引入 node 依赖。

3.5.1 配置方式

约定 src/app.tsx 为运行时配置。

3.5.2 配置项
3.5.2.1 modifyClientRenderOpts(fn)

修改 clientRender 参数。

比如在微前端里动态修改渲染根节点:

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

修改路由。

比如在最前面添加一个 /foo 路由,

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

比如和 render 配置配合使用,请求服务端根据响应动态更新路由,

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();
  })
}

注意:

  • 直接修改routes,不需要返回
3.5.2.3 render(oldRender: Function)

覆写 render。

比如用于渲染之前做权限校验,

import { history } from 'umi';
export function render(oldRender) {
  fetch('/api/auth').then(auth => {
    if (auth.isLogin) { oldRender() }
    else { 
      history.push('/login'); 
      oldRender()
    }
  });
}
3.5.2.4 onRouteChange({ routes, matchedRoutes, location, action })

在初始加载和路由切换时做一些事情。

比如用于做埋点统计,

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

比如用于设置标题,

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

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

比如用于在外面包一个 Provider,

export function rootContainer(container) {
  return React.createElement(ThemeProvider, null, container);
}

args 包含:

  • routes,全量路由配置
  • plugin,运行时插件机制
  • history,history 实例
3.5.3 更多配置项

Umi 允许插件注册运行时配置,如果你使用插件,肯定会在插件里找到更多运行时的配置项。

四、路由

在 Umi 中,应用都是单页应用,页面地址的跳转都是在浏览器端完成的,不会重新请求服务端获取 html,html 只在应用初始化时加载一次。所有页面由不同的组件构成,页面的切换其实就是不同组件的切换,你只需要在配置中把不同的路由路径和对应的组件关联上。

4.1 配置路由

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

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

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

4.1.2 component
  • Type: string

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

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

4.1.3 exact
  • Type: boolean
  • Default: true

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

比如:

export default {
  routes: [
    // url 为 /one/two 时匹配失败
    { path: '/one', exact: true },
    
    // url 为 /one/two 时匹配成功
    { path: '/one' },
    { path: '/one', exact: false },
  ],
}
4.1.4 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 组件。

4.1.5 redirect
  • Type: string

配置路由跳转。

比如:

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

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

4.1.6 wrappers
  • Type: 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 进行渲染。

4.1.7 title
  • Type: string

配置路由的标题。

4.2 页面跳转

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

4.3 hash 路由

详见 配置#history

4.4 Link 组件

import { Link } from 'umi';
export default () => (
  <div>
    <Link to="/users">Users Page</Link>
  </div>
);

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

注意:

  • Link 只用于单页应用的内部跳转,如果是外部地址跳转请使用 a 标签

4.5 路由组件参数

路由组件可通过 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>;
}

4.6 传递参数给子路由

通过 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' });
  });
}

4.7 约定式路由

除配置式路由外,Umi 也支持约定式路由。约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。

如果没有 routes 配置,Umi 会进入约定式路由模式,然后分析 src/pages 目录拿到路由配置。

比如以下文件结构:

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

会得到以下路由配置,

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

需要注意的是,满足以下任意规则的文件不会被注册为路由,

  • 以 . 或 _ 开头的文件或目录
  • 以 d.ts 结尾的类型定义文件
  • 以 test.ts、spec.ts、e2e.ts 结尾的测试文件(适用于 .js、.jsx 和 .tsx 文件)
  • components 和 component 目录
  • utils 和 util 目录
  • 不是 .js、.jsx、.ts 或 .tsx 文件
  • 文件内容不包含 JSX 元素

4.8 动态路由

约定 [] 包裹的文件或文件夹为动态路由。

比如:

  • src/pages/users/[id].tsx 会成为 /users/:id
  • src/pages/users/[id]/settings.tsx 会成为 /users/:id/settings

举个完整的例子,比如以下文件结构,

.
  └── pages
    └── [post]
      ├── index.tsx
      └── comments.tsx
    └── users
      └── [id].tsx
    └── index.tsx

会生成路由配置,

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

4.9 动态可选路由

约定 [ $] 包裹的文件或文件夹为动态可选路由。

比如:

  • src/pages/users/[id$].tsx 会成为 /users/:id?
  • src/pages/users/[id$]/settings.tsx 会成为 /users/:id?/settings

举个完整的例子,比如以下文件结构,

.
  └── pages
    └── [post$]
      └── comments.tsx
    └── users
      └── [id$].tsx
    └── index.tsx

会生成路由配置,

[
  { exact: true, path: '/', component: '@/pages/index' },
  { exact: true, path: '/users/:id?', component: '@/pages/users/[id$]' },
  {
    exact: true,
    path: '/:post?/comments',
    component: '@/pages/[post$]/comments',
  },
];

4.10 嵌套路由

Umi 里约定目录下有 _layout.tsx 时会生成嵌套路由,以 _layout.tsx 为该目录的 layout。layout 文件需要返回一个 React 组件,并通过 props.children 渲染子组件。

比如以下目录结构,

.
└── 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' },
    ]
  }
]

4.11 全局 layout

约定 src/layouts/index.tsx 为全局路由。返回一个 React 组件,并通过 props.children 渲染子组件。

比如以下目录结构,

.
└── src
    ├── layouts
    │   └── index.tsx
    └── pages
        ├── index.tsx
        └── users.tsx

会生成路由,

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

一个自定义的全局 layout 如下:

import { IRouteComponentProps } from 'umi'
export default function Layout({ children, location, route, history, match }: IRouteComponentProps) {
  return children
}

4.12 不同的全局 layout

你可能需要针对不同路由输出不同的全局 layout,Umi 不支持这样的配置,但你仍可以在 src/layouts/index.tsx 中对 location.path 做区分,渲染不同的 layout 。

比如想要针对 /login 输出简单布局,

export default function(props) {
  if (props.location.pathname === '/login') {
    return <SimpleLayout>{ props.children }</SimpleLayout>
  }
  return (
    <>
      <Header />
      { props.children }
      <Footer />
    </>
  );
}

4.13 404 路由

约定 src/pages/404.tsx 为 404 页面,需返回 React 组件。

比如以下目录结构,

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

会生成路由,

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

这样,如果访问 /foo,/ 和 /users 都不能匹配,会 fallback 到 404 路由,通过 src/pages/404.tsx 进行渲染。

4.14 权限路由

通过指定高阶组件 wrappers 达成效果。

如下,src/pages/user

import React from 'react'
function User() {
  return <>user profile</>
}
User.wrappers = ['@/wrappers/auth']
export default User

然后在 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 进行渲染。

4.15 扩展路由属性

支持在代码层通过导出静态属性的方式扩展路由。

比如:

function HomePage() {
  return <h1>Home Page</h1>;
}

HomePage.title = 'Home Page';

export default HomePage;

其中的 title 会附加到路由配置中。

五、插件

插件的 id 和 key

每个插件都会对应一个 id 和一个 key,id 是路径的简写,key 是进一步简化后用于配置的唯一值。

比如插件 /node_modules/@umijs/plugin-foo/index.js,通常来说,其 id 为 @umijs/plugin-foo,key 为 foo。

六、页面跳转

在 umi 里,页面之间跳转有两种方式:声明式和命令式。

6.1 声明式

通过 Link 使用,通常作为 React 组件使用。

import { Link } from 'umi';
export default () => (
  <Link to="/list">Go to list page</Link>
);

6.2 命令式

通过 history 使用,通常在事件处理中被调用。

import { history } from 'umi';
function goToListPage() {
  history.push('/list');
}

也可以直接从组件的属性中取得 history

export default (props) => (
  <Button onClick={()=>props.history.push('/list');}>Go to list page</Button>
);

七、HTML 模板

7.1 修改默认模板

新建 src/pages/document.ejs,umi 约定如果这个文件存在,会作为默认模板,比如:

<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Your App</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

7.2 配置模板

模板里可通过 context 来获取到 umi 提供的变量,context 包含:

  • route,路由信息,需要打包出多个静态 HTML 时(即配置了 exportStatic 时)有效
  • config,用户配置信息

比如:

<link rel="icon" type="image/x-icon" href="<%= context.config.publicPath %>favicon.png" />

八、Mock 数据

Mock 数据是前端开发过程中必不可少的一环,是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口,模拟请求数据甚至逻辑,能够让前端开发独立自主,不会被服务端的开发所阻塞。

8.1 约定式 Mock 文件

Umi 约定 /mock 文件夹下所有文件为 mock 文件。

比如:

.
├── mock
    ├── api.ts
    └── users.ts
└── src
    └── pages
        └── index.tsx

/mock 下的 api.tsusers.ts 会被解析为 mock 文件。

8.2 编写 Mock 文件

export default {
  // 支持值为 Object 和 Array
  'GET /api/users': { users: [1, 2] },
  // GET 可忽略
  '/api/users/1': { id: 1 },
  // 支持自定义函数,API 参考 express@4
  'POST /api/users/create': (req, res) => {
    // 添加跨域请求头
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.end('ok');
  },
}

然后访问 /api/users 就能得到 { users: [1,2] } 的响应,其他以此类推。

8.3 配置 Mock

详见 配置#mock

8.4 如何关闭 Mock?

可以通过配置关闭,

export default {
  mock: false,
};

也可以通过环境变量临时关闭,

$ MOCK=none umi dev

8.5 引入 Mock.js

Mock.js 是常用的辅助生成模拟数据的三方库,借助他可以提升我们的 mock 数据能力。

import mockjs from 'mockjs';
export default {
  // 使用 mockjs 等三方库
  'GET /api/tags': mockjs.mock({
    'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
  }),
};

九、环境变量

9.1 设置环境变量

9.1.1 执行命令时添加

比如,

# OS X, Linux
$ PORT=3000 umi dev
# Windows (cmd.exe)
$ set PORT=3000&&umi dev

如果要同时考虑 OS X 和 Windows,可借助三方工具 cross-env,

$ yarn add cross-env --dev
$ cross-env PORT=3000 umi dev
9.1.2 在 .env 文件中定义

Umi 中约定根目录下的 .env 为环境变量配置文件。

比如:

PORT=3000
BABEL_CACHE=none

然后执行,

$ umi dev

会以 3000 端口启动 dev server,并且禁用 babel 的缓存。

9.2 环境变量列表

按字母排序。

9.2.1 APP_ROOT

指定项目根目录。

注意:APP_ROOT 不能配在 .env 中,只能在命令行里添加

9.2.2 ANALYZE

用于分析 bundle 构成,默认关闭。

$ ANALYZE=1 umi dev
# 或者
$ ANALYZE=1 umi build
9.2.3 ANALYZE_SSR

对服务端包大小的分析,默认关闭

9.2.4 BABEL_CACHE

默认开启 Babel 编译缓存,值为 none 时禁用缓存。

9.2.5 BABEL_POLYFILL

默认会根据 targets 配置打目标浏览器的全量补丁,设置为 none 禁用内置的补丁方案。

9.2.6 COMPRESS

默认压缩 CSS 和 JS,值为 none 时不压缩,build 时有效。

9.2.7 FORK_TS_CHECKER

默认不开启 TypeScript 类型检查,值为 1 时启用。比如:

$ FORK_TS_CHECKER=1 umi dev
9.2.8 FRIENDLY_ERROR

设为 none 时禁用,有些场景下 friendly-errors-webpack-plugin 会把错误给吞了

$ FRIENDLY_ERROR=none umi dev
9.2.9 HTTPS

localhost 开启 https

$ HTTPS=1 umi dev

同时也可以使用配置 https: { key: '/path/key.pem', cert: '/path/cert.pem' } 自定义证书。

9.2.10 HMR

设为 none 时禁用代码热更新功能。

9.2.11 HTML

设为 none 时不输出 HTML,umi build 时有效。

9.2.12 HOST

默认是 0.0.0.0

9.2.13 PORT

指定端口号,默认是 8000。

9.2.14 PROGRESS

设为 none 时禁用进度条。比如:

$ PROGRESS=none umi dev
9.2.15 SOCKET_SERVER

指定用于 HMR 的 socket 服务器。比如:

$ SOCKET_SERVER=https://localhost:7001/ umi dev
9.2.16 SPEED_MEASURE

分析 Webpack 编译时间,支持 CONSOLE 和 JSON 两种格式,默认是 JSON。

$ SPEED_MEASURE=CONSOLE umi dev
9.2.17 TERSER_CACHE

默认开启 Terser 压缩缓存,值为 none 时禁用缓存。

9.2.18 UMI_ENV

指定不同环境各自的配置文件,详见配置#多环境多份配置。

9.2.19 WATCH

设为 none 时不监听文件变更。比如:

$ WATCH=none umi dev
9.2.20 WATCH_IGNORED

默认不监听 node_modules 下的文件修改,如果需要,可通过此环境变量进行设置。比如:

# 整个 node_modules 都监听,会慢点
WATCH_IGNORED=none umi dev
# node_modules 下除 lodash 和 umi 监听,其他忽略
WATCH_IGNORED=node_modules/(?!(lodash|umi)) umi dev
9.2.21 WEBPACK_PROFILE

生成 umi 构建性能分析文件 dist/stats.json,结合 Webpack Xray 或 Webapck Analyse ,WEBPACK_PROFILE 值有 verbose、normal、minimal。比如:

$ WEBPACK_PROFILE=verbose umi build
9.2.22 RM_SERVER_FILE

预渲染下,默认会删除服务端渲染文件 umi.server.js,如果希望保留,使用 RM_SERVER_FILE=none

十、使用 CSS

本文档以 css 为示例,把后缀换成 .less 同样适用。

10.1 全局样式

Umi 中约定 src/global.css 为全局样式,如果存在此文件,会被自动引入到入口文件最前面。

比如用于覆盖样式,

html, body, #root {
  height: 100%;
}

body {
  margin: 0;
}

10.2 CSS Modules

Umi 会自动识别 CSS Modules 的使用,你把他当做 CSS Modules 用时才是 CSS Modules。

比如:

// CSS Modules
import styles from './foo.css';
// 非 CSS Modules
import './foo.css';

10.3 CSS 预处理器

Umi 内置支持 less,不支持 sass 和 stylus,但如果有需求,可以通过 chainWebpack 配置或者 umi 插件的形式支持。

10.4 CSS 中引入三方库

TODO:别名的使用。

十一、使用图片

11.1 JS 里使用图片

通过 require 引用相对路径的图片。

export default () => <img src={require('./foo.png')} />

支持别名,比如通过 @ 指向 src 目录:

export default () => <img src={require('@/foo.png')} />

11.2 JS 里使用svg

11.2.1 组件式引入

import { ReactComponent as Logo } from './logo.svg'
function Analysis() {
  return <Logo width={90} height={120} />
}

11.2.2 url式引入

import logoSrc from './logo.svg'
function Analysis() {
  return <img src={logoSrc} alt="logo" />
}

11.3 CSS 里使用图片

通过相对路径引用。

.logo {
  background: url(./foo.png);
}

CSS 里也支持别名,但需要在前面加 ~ 前缀,

.logo {
  background: url(~@/foo.png);
}

注意:

  1. 这是 webpack 的规则,如果切到其他打包工具,可能会有变化
  2. less 中同样适用

11.4 图片路径问题

项目中使用图片有两种方式,

  1. 先把图片传到 cdn,然后在 JS 和 CSS 中使用图片的绝对路径
  2. 把图片放在项目里,然后在 JS 和 CSS 中通过相对路径的方式使用

前者不会有任何问题;后者,如果在 JS 中引用相对路径的图片时,在发布时会根据 publicPath 引入绝对路径,所以就算没有开启 dynamicImport 时,也需要注意 publicPath 的正确性。

11.5 Base64 编译

通过相对路径引入图片的时候,如果图片小于 10K,会被编译为 Base64,否则会被构建为独立的图片文件。

10K 这个阈值可以通过 inlineLimit 配置修改。

十二、按需加载

12.1 启用按需加载

常见使用场景:组件体积太大,不适合直接计入 bundle 中,以免影响首屏加载速度。例如:某组件 HugeA 包含巨大的实现 / 依赖了巨大的三方库,且该组件 HugeA 的使用不在首屏显示范围内,可被单独拆出。这时候,dynamic 就该上场了。

为了简化部署成本,按需加载功能默认是关闭的,你需要在使用之前先通过配置开启,

export default {
  dynamicImport: {},
}

12.2 使用按需加载

按需加载组件 dynamic

为什么使用 dynamic:封装了使用一个异步组件需要做的状态维护工作,开发者可以更专注于自己的业务组件开发,而不必关心 code splitingasync module loading 等等技术细节。

通常搭配 动态 import 语法 使用。

封装一个异步组件

import { dynamic } from 'umi';
export default dynamic({
  loader: async function() {
    // 这里的注释 webpackChunkName 可以指导 webpack 将该组件 HugeA 以这个名字单独拆出去
    const { default: HugeA } = await import(/* webpackChunkName: "external_A" */ './HugeA');
    return HugeA;
  },
});

使用异步组件

import React from 'react';
import AsyncHugeA from './AsyncHugeA';
// 像使用普通组件一样即可
// dynamic 为你做:
// 1. 异步加载该模块的 bundle
// 2. 加载期间 显示 loading(可定制)
// 3. 异步组件加载完毕后,显示异步组件
export default () => {
  return <AsyncHugeA />;
}

十三、快速刷新(Fast Refresh)

快速刷新(Fast Refresh)是 React 官方为 React Native 开发的模块热替换(HMR)方案,由于其核心实现与平台无关,同时也适用于 Web。

Fast Refresh 功能最大的特性是:开发环境下,可以保持组件状态,同时编辑提供即时反馈。

13.1 怎样使用?

在配置文件加上 fastRefresh: {} 即可开启

这张 gif 动图展示的是使用 Fast Refresh 特性的开发体验,可以看出,修改组件代码后,用户名和密码状态保持,这将提升应用本地研发体验。

在这里插入图片描述
开发方式上与平时没有区别,正常地修改、保存、预览,只是在效果反馈上,体验更加好。

13.2 限制

有些情况下,维持状态并不是预期,所以为了可靠起见,Fast Refresh 遇到以下情况一概不保留状态(remount):

  • Class 类组件一律重刷(remount),状态会被重置,包括高阶组件返回的 Class 组件
  • 不纯组件模块,所编辑的模块除导出 React 组件外,还导出了其它模块
  • 匿名箭头函数如 export default () => <div />; 会导致状态丢失
  • 特殊的,还可以通过 // @refresh reset 指令(在源码文件中任意位置加上这行注释)强制重刷(remount),最大限度地保证可用性

13.3 技巧

推荐写函数命名组件,例如:

const Foo = () => {};
export default Foo;

十四、部署

14.1 默认方案

Umi 默认对新手友好,所以默认不做按需加载处理,umi build 后输出 index.htmlumi.jsumi.css 三个文件。

14.2 不输出 html 文件

某些场景 html 文件交给后端输出,前端构建并不需要输出 html 文件,可配置环境变量 HTML=none 实现。

$ HTML=none umi build

14.3 部署 html 到非根目录

经常有同学问这个问题:为什么我本地开发是好的,部署后就没反应了,而且没有报错?

没有报错! 这是应用部署在非根路径的典型现象。为啥会有这个问题?因为路由没有匹配上,比如你把应用部署在 /xxx/ 下,然后访问 /xxx/hello,而代码里匹配的是 /hello,那就匹配不上了,而又没有定义 fallback 的路由,比如 404,那就会显示空白页。

可通过配置 base 解决。

export default {
  base: '/path/to/your/app/root',
};

14.4 使用 hash history

可通过配置 history 为 hash 为解决。

export default {
  history: { type: 'hash' },
};

14.5 按需加载

要实现按需加载,需配置 dynamicImport。

export default {
  dynamicImport: {},
};

14.6 静态资源在非根目录或 cdn

这时,就需要配置 publicPath。至于 publicPath 是啥?具体看 webpack 文档,把他指向静态资源(js、css、图片、字体等)所在的路径。

export default {
  publicPath: "http://yourcdn/path/to/static/"
}

14.7 使用 runtime 的 publicPath

对于需要在 html 里管理 publicPath 的场景,比如在 html 里判断环境做不同的输出,可通过配置 runtimePublicPath 为解决。

export default {
  runtimePublicPath: true,
};

然后在 html 里输出:

<script>
window.publicPath = <%= YOUR PUBLIC_PATH %>
</script>

14.8 静态化

在一些场景中,无法做服务端的 html fallback,即让每个路由都输出 index.html 的内容,那么就要做静态化。

比如上面的例子,我们在 .umirc.js 里配置:

export default {
  exportStatic: {},
}

然后执行 umi build,会为每个路由输出一个 html 文件。

./dist
├── index.html
├── list
│   └── index.html
└── static
    ├── pages__index.5c0f5f51.async.js
    ├── pages__list.f940b099.async.js
    ├── umi.2eaebd79.js
    └── umi.f4cb51da.css

注意:静态化暂不支持有变量路由的场景。

14.9 HTML 后缀

有些静态化的场景里,是不会自动读索引文件的,比如支付宝的容器环境,那么就不能生成这种 html 文件,

├── index.html
├── list
│   └── index.html

而是生成,

├── index.html
└── list.html

配置方式是在 .umirc.js 里,

export default {
  exportStatic: {
    htmlSuffix: true,
  },
}

umi build 会生成,

./dist
├── index.html
├── list.html
└── static
    ├── pages__index.5c0f5f51.async.js
    ├── pages__list.f940b099.async.js
    ├── umi.2924fdb7.js
    └── umi.cfe3ffab.css

14.10 静态化后输出到任意路径

export default {
  exportStatic: {
    htmlSuffix: true,
    dynamicRoot: true,
  },
}

十五、使用 Umi UI

由于 Umi 3 使用微内核架构,将之前 Umi UI 拆分到独立的仓库,通过加载 @umijs/preset-ui 使用 Umi UI。

Umi 项目的本地研发工作台
在这里插入图片描述

15.1 特性

  • 项目管理。集中式管理本地项目。
  • 配置管理。umi / bigfish 常用项目配置。
  • 任务管理。集成启动、构建、测试、代码规范检查、重新安装依赖等常用操作。
  • 资产、区块管理

15.2 使用

$ yarn add @umijs/preset-ui -D
$ UMI_UI=1 umi dev

十六、服务端渲染(SSR)

16.1 什么是服务端渲染?

服务端渲染(Server-Side Rendering),是指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。

这么讲可能比较学术,那通过两张图来更容易地说清楚。

第一张,单页应用(SPA)和服务端渲染过的(SSR)站点在社交分享时的区别:
在这里插入图片描述
第二张,白屏时间上 SSR 较少,因为当 HTML 文档返回时,已经有对应的内容。(
在这里插入图片描述
综上两图可知,SSR 常用于以下两个场景:

  1. 有 SEO 诉求,用在搜索引擎检索以及社交分享,用在前台类应用。
  2. 首屏渲染时长有要求,常用在移动端、弱网情况下。

也就是说,如果你是中后台应用(如 antd pro、管理后台等),请谨慎考虑是否使用 SSR。

16.2 什么是预渲染?

服务端渲染,首先得有后端服务器(一般是 Node.js)才可以使用,如果我没有后端服务器,也想用在上面提到的两个场景,那么推荐使用预渲染

预渲染与服务端渲染唯一的不同点在于渲染时机,服务端渲染的时机是在用户访问时执行渲染(即实时渲染,数据一般是最新的),预渲染的时机是在项目构建时,当用户访问时,数据不一定是最新的(如果数据没有实时性,则可以直接考虑预渲染)。

预渲染(Pre Render)在构建时执行渲染,将渲染后的 HTML 片段生成静态 HTML 文件。无需使用 web 服务器实时动态编译 HTML,适用于静态站点生成

16.3 Umi 服务端渲染特性

Umi 3 结合自身业务场景,在 SSR 上做了大量优化及开发体验的提升,具有以下特性:

  • 开箱即用:内置 SSR,一键开启,umi dev 即 SSR 预览,开发调试方便。
  • 服务端框架无关:Umi 不耦合服务端框架(例如 Egg.js、Express、Koa),无论是哪种框架或者 Serverless 模式,都可以非常简单进行集成。
  • 支持应用和页面级数据预获取:Umi 3 中延续了 Umi 2 中的页面数据预获取(getInitialProps),来解决之前全局数据的获取问题。
  • 支持按需加载:按需加载 dynamicImport 开启后,Umi 3 中会根据不同路由加载对应的资源文件(css/js)。
  • 内置预渲染功能:Umi 3 中内置了预渲染功能,不再通过安装额外插件使用,同时开启 ssr 和 exportStatic,在 umi build 构建时会编译出渲染后的 HTML。
  • 支持渲染降级:优先使用 SSR,如果服务端渲染失败,自动降级为客户端渲染(CSR),不影响正常业务流程。
  • 支持流式渲染:ssr: { mode: ‘stream’ } 即可开启流式渲染,流式 SSR 较正常 SSR 有更少的 TTFB(发出页面请求到接收到应答数据第一个字节所花费的毫秒数) 时间。
  • 兼容客户端动态加载:在 Umi 2 中同时使用 SSR 和 dynamicImport(动态加载)会有一些问题,在 Umi 3 中可同时开启使用。
  • SSR 功能插件化:Umi 3 内置的 SSR 功能基本够用,若不满足需求或者想自定义渲染方法,可通过提供的 API 来自定义。

16.4 启用服务端渲染

默认情况下,服务端渲染功能是关闭的,你需要在使用之前通过配置开启:

export default {
  ssr: {},
}
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值