UmiJS基础&UmiJS+Dva

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

1️⃣了解Umi

你可以将Umi简单的理解为一个专注性能的类 next.js 前端框架,并通过约定、自动生成和解析代码等方式来辅助开发,减少我们开发者的代码量。

功能特点

  • 开箱即用,Umi 内置了路由、构建、部署、测试等,仅需一个依赖即可上手开发。
  • 完善的插件体系,Umi 实现了完整的生命周期,并使其插件化。此外还提供针对 React 的集成插件集。
  • 完备路由,同时支持配置式路由和约定式路由,同时保持功能的完备性,比如动态路由、嵌套路由、权限路由等等。

技术收敛

《蚂蚁金服前端框架和工程化实践 》

蚂蚁金服前端框架
在这里插入图片描述
在这里插入图片描述
Umi把大家常用的技术栈进行整理,收敛到一起,让大家只用 Umi 就可以完成 80% 的日常工作。
在这里插入图片描述
Bigfish 企业级前端开发框架,与常见的前端框架和工具不同,它从工程角度集成了各类功能,从初始化开发到最终上线整套技术方案,解决了前端开发经常遇到的前端技术栈整合配置麻烦、开发联调麻烦、前端资源发布上线麻烦三大痛点。

创建第一个Umi应用

# 使用 yarn 管理 npm 依赖 使用国内源 之后使用tyarn代替yarn即可
$ npm i yarn tyarn -g

# 创建目录
$ mkdir myapp 

# 进入目录
$ cd myapp

# 通过官方工具创建项目
$ yarn create @umijs/umi-app

# 安装依赖
$ yarn

# 启动开发
$ yarn start

启动页面效果 和 当前项目结构
在这里插入图片描述

.umi 临时文件

.umi 临时目录是整个 Umi 项目的发动机,你的入口文件、路由等等都在这里,这些是由 umi 内部插件及三方插件生成的。

+ .umi
  + core     # 内部插件生成
  + pluginA  # 外部插件生成
  + presetB  # 外部插件生成
  + umi.ts   # 入口文件

临时文件是 Umi 框架中非常重要的一部分,框架或插件会根据你的代码生成临时文件,这些原来需要放在项目里的脏乱差的部分都被藏在了这里。

你可以在这里调试代码,但不要在 .git 仓库里提交他,因为他的临时性,每次启动 umi 时都会被删除并重新生成。

修改配置换个页面布局

默认的脚手架内置了 @umijs/preset-react,包含布局、权限、国际化、dva、简易数据流等常用功能。

想使用 ant-design-pro 的布局

  1. 添加@ant-design/pro-layout依赖
yarm add @ant-design/pro-layout
  1. 编辑 .umirc.ts文件,加入布局配置项 +layout:{},
import { defineConfig } from 'umi';

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

在这里插入图片描述

部署发布

📌构建

$ yarn build

构建产物默认生成到 ./dist 下
在这里插入图片描述
📌本地验证
发布之前,可以通过 serve 做本地验证,

# 安装serve服务
npm i serve -g

# 启动发布的项目包
serve ./dist

在这里插入图片描述
访问 http://localhost:5000,正常情况下应该是和执行 yarn start 时是一致的。

2️⃣Umi基础

目录结构

一个基础的 Umi 项目大致是这样的

.
├── package.json		包含插件和插件集
├── .umirc.ts			配置文件,包含 umi 内置功能和插件的配置。
├── .env				环境变量。比如:PORT=8888
├── dist				打包产物默认会存放在这里。
├── mock				存储 mock 文件,此目录下所有 js 和 ts 文件会被解析为 mock 文件。
├── public				此目录下所有文件会被 copy 到输出路径。
├── config				项目复杂时候独立出来的的子配置文件,比如:routes.ts
└── src
    ├── .umi			临时文件目录,比如入口文件、路由等,都会被临时生成到这里。
    ├── layouts/index.tsx          约定式路由时的全局布局文件。
    ├── pages			所有路由组件存放在这里。
        ├── index.less
        └── index.tsx
    └── app.ts			运行时配置文件,可以在这里扩展运行时的能力,比如修改路由、修改 render 方法等。

配置

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

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

👉全部的配置项

defineConfig 让配置有代码提示

使用defineConfig方法可以让我们在写配置的时候有代码提示

import { defineConfig } from 'umi';

export default defineConfig({
  routes: [
    { path: '/', component: '@/pages/index' },
  ],
});
本地临时配置

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

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

// .umirc.ts 
export default { a: 1, b: 2 };

// .umirc.local.ts
export default { b: 3,c: 'local' };

拿到的配置是:

{
  a: 1,
  b: 3,
  c: 'local',
}
  • .local.ts 是本地验证使用的临时配置,请将其添加到 .gitignore务必不要提交到 git 仓库中
  • .local.ts 配置的优先级最高,比 UMI_ENV 指定的配置更高
多环境多份配置

可以通过环境变量 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',
}

📌如何指定UMI_ENV?
在这里插入图片描述
.env文件配置
在这里插入图片描述

运行时配置

运行时配置和配置的区别是前者跑在浏览器端而后者是在服务器端,所以运行时配置:

  • 不需要引入 node 依赖
  • 可以写函数、jsx、import 浏览器端依赖
配置方式

约定(俗成) src/app.tsx 为运行时配置。在 src/app.ts 中你可以配置一些运行时的配置项来实现部分自定义需求。

📌 request 运行时配置示例:

import { RequestConfig } from 'umi';

export const request: RequestConfig = {
  timeout: 1000,
  errorConfig: {},
  middlewares: [],
  requestInterceptors: [],
  responseInterceptors: [],
};

📌dva 运行时配置示例:

import { createLogger } from 'redux-logger';
import { message } from 'antd';

export const dva = {
  config: {
    onAction: createLogger(),
    onError(e: Error) {
      message.error(e.message, 3);
    },
  },
};

路由

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

配置路由

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

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

📌 path   [Type: `string` ]

也可以是路径正则表达式


📌 component  [Type: string ]

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

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


📌 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 },
  ],
}

📌 routes

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

// config/routes.tsx
export default [
  {
    path: '/ex', component: '@/pages/layouts', routes: [
      { path: '/ex/list', component: '@/pages/list' },
      { path: '/ex/admin', component: '@/pages/admin' }
    ]
  },
]

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

// src/layouts/index.tsx
export default (props)=>{
    return <>{props.children}</>
}

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

// src/pages/list.tsx
export default function list(){
    return (<h1>list</h1>);
}
// src/pages/admin.tsx
export default function admin(){
    return (<h1>admin</h1>);
}

在这里插入图片描述


📌 redirect  [Type: string ]

配置路由跳转。

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

在这里插入图片描述
访问 / 会跳转到 /list,并由 src/pages/list 文件进行渲染。


📌 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 进行渲染。


📌 title [Type: string[] ]

配置路由的标题。

页面跳转

命令式页面跳转:通过 history 使用,通常在事件处理中被调用。

import { history } from 'umi';

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

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

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

声明式页面跳转

import { Link } from 'umi';

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

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

约定式路由

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

如果没有 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 元素

在这里插入图片描述

动态路由

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

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

在这里插入图片描述

动态可选路由

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

比如:

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

在这里插入图片描述
在这里插入图片描述

嵌套路由

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

在这里插入图片描述

// _layout.tsx
export default (props: { children: any })=>{
    return <>layout{props.children}</>
}

在这里插入图片描述

全局 layout

约定 src/layouts/index.tsx 为全局路由。返回一个 React 组件,并通过 props.children 渲染子组件。
在这里插入图片描述

export default (props: { children: any })=>{
  return <>
  <h1>全局</h1>
  {props.children}</>
}

在这里插入图片描述

不同的全局 layout

在 src/layouts/index.tsx 中对 location.path 做区分,渲染不同的 layout 。

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

  return (
    <>
      <Header />
      { props.children }
      <Footer />
    </>
  );
}
404 路由

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

权限路由

通过指定高阶组件 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 进行渲染。

插件

📌插件的 id 和 key

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

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

启用插件

📌package.json 依赖

Umi 会自动检测 dependencies 和 devDependencies 里的 umi 插件,比如:

{
  "dependencies": {
    "@umijs/preset-react": "1"
  }
}

那么 @umijs/preset-react 会自动被注册,无需在配置里重复声明。

📌配置 .umirc.ts

在配置里可通过 presets 和 plugins 配置插件,比如:

export default {
  presets: ['./preset', 'foo/presets'],
  plugins: ['./plugin'],
}
禁用插件

📌配置 key 为 false

export default {
  mock: false,
}
检查插件注册情况
$ umi plugin list

# 顺便看看他们分别用了哪些 key
$ umi plugin list --key

拓展:按需加载插件

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

启用按需加载

按需加载功能默认是关闭的,需要通过配置开启

export default {
  dynamicImport: {},
}
使用按需加载

dynamic是一个按需加载组件,封装了使用一个异步组件需要做的状态维护工作

📌封装一个异步组件

//AsyncHugeA.tsx
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 />;
}

HTML模板

修改默认模板

新建 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>

修改title,直接在.umirc.ts加入title配置项即可

配置模板

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

  • route,路由信息,需要打包出多个静态 HTML 时(即配置了 exportStatic 时)有效
  • config,用户配置信息 .umirc.ts
<link rel="icon" type="image/x-icon" href="<%= context.config.publicPath %>favicon.png" />

Mock 数据

约定式 Mock 文件

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

.
├── mock
    ├── api.ts
    └── users.ts

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

编写 Mock 文件
'requestType requestPath':response

如 /mock/api.ts 的内容如下,

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');
  },
}
引入 Mock.js
yarn add @types/mockjs -D
import mockjs from 'mockjs';

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

常用的环境变量

完整的环境变量

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

//.env
PORT=3000 
HTTPS=1
ANALYZE

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

ANALYZE=1 

在这里插入图片描述

FORK_TS_CHECKER

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

FORK_TS_CHECKER=1
HTTPS

localhost 开启 https

HTTPS=1

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

PORT

指定端口号,默认是 8000。

UMI_ENV

指定不同环境各自的配置文件

UMI_ENV=cloud
// .umirc.js 
export default { a: 1, b: 2 };

// .umirc.cloud.js 
export default { b: 'cloud', c: 'cloud' };

// .umirc.local.js 
export default { c: 'local' };

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

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

3️⃣样式和资源文件

以下虽然使用.css示例,写成.less效果一样

使用 CSS

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

全局样式

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

CSS Modules

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

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

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

使用图片

JS 里使用图片
import logoSrc from './logo.svg'

 <img src={'http://www.ivotoo.com/test/1.jpg'}/>
 <img src={require('../../public/images/1.jpeg')}/>
 <img src={logoSrc} />

//支持别名,比如通过 @ 指向 src 目录:
<img src={require('@/foo.png')} />

如果是svg类型,还可以组件式引入:

import { ReactComponent as Logo } from './logo.svg'

function Analysis() {
  return <Logo width={90} height={120} />
}
CSS 里使用图片

通过相对路径引用:

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

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

.logo {
  background: url(~@/foo.png);
}
图片路径问题

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

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

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

Base64 编译

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

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

4️⃣Umi进阶

使用dva models

我们在创建项目的时候引入了@umijs/preset-react ,这是一个插件集,包含:

  • plugin-access,权限管理
  • plugin-analytics,统计管理
  • plugin-antd,整合 antd UI 组件
  • plugin-crossorigin,通常用于 JS 出错统计
  • plugin-dva,整合 dva
  • plugin-helmet,整合 react-helmet,管理 HTML 文档标签(如标题、描述等)
  • plugin-initial-state,初始化数据管理
  • plugin-layout,配置启用 ant-design-pro 的布局
  • plugin-locale,国际化能力
  • plugin-model,基于 hooks 的简易数据流
  • plugin-request,基于 umi-request 和 umi-hooks 的请求方案

所以无需再而外安装 plugin-dva ,至于以上插件的用法,看UmiJS Plugins

开启dva

dva 配置有没有开启,该插件是配置开启的
只需要在配置中开启即可。打开 umi 的配置文件:

//.umirc.js
import { defineConfig } from 'umi';

export default defineConfig({
  dva: {},
  antd: {}
});
新增 model 文件
约定式的 model 组织方式
符合以下规则的文件会被认为是 model 文件,

src/models 下的文件
src/pages 下,子目录中 models 目录下的文件
src/pages 下,所有 model.ts 文件

比如:
+ src
  + models/a.ts
  + pages
    + foo/models/b.ts
    + bar/model.ts
其中 a.ts,b.ts 和 model.ts 如果其内容是有效 dva model 写法,则会被认为是 model 文件。
// src/models/hero.ts
import { Effect, Reducer } from 'umi';

export interface HeroModelState {
  name: string;
}

export interface HeroModelType {
  namespace: 'hero';
  state: HeroModelState;
  effects: {
    query: Effect;
  };
  reducers: {
    save: Reducer<HeroModelState>;
  };
}

const HeroModel: HeroModelType = {
  namespace: 'hero',

  state: {
    name: 'hero',
  },

  effects: {
    *query({ payload }, { call, put }) {

    },
  },
  reducers: {
    save(state, action) {
      return {
        ...state,
        ...action.payload,
      };
    },
  },
};

export default HeroModel;

如果文件中的 namespace 未写明,umi 会使用文件名作为 model 的 namespace。为了减少错误的出现,最好保持所有的 model 文件,文件名不同。

在页面中使用model
// src/pages/hero.tsx

import React, { FC } from 'react';
import styles from './hero.css';
import { connect, HeroModelState, ConnectProps } from 'umi';  ---step1


interface PageProps extends ConnectProps {
  hero: HeroModelState;
}

const Hero: FC<PageProps> = (props) => {         ---step2
  console.log(props.hero);      ---step4
  return (
    <div>
      <h1 className={styles.title}>Page hero</h1>
      <h2>This is {props.hero.name}</h2>
    </div>
  );
}
export default connect(({ hero }: { hero: HeroModelState }) => ({ hero }))(Hero);
  --- step3

  • step1 在文件头部引入了 umi 的connect,HeroModelState, ConnectProps
  • step2 把之前的匿名函数,改成实名函数Hero
  • step3 使用connect连接页面和models
  • step4 从属性中取出namespace为hero的model的state数据。

监听路由事件

进入页面的时候,发起请求页面初始化数据。这里我们通过 dva model 的 subscriptions 实现。

src/models/hero.ts subscriptions

import { Effect, Reducer, Subscription } from 'umi';

export interface HeroModelType {
  namespace: 'hero';
  state: HeroModelState;
  effects: {
    query: Effect;
  };
  reducers: {
    save: Reducer<HeroModelState>;
  };
+  subscriptions: { setup: Subscription };
}
HeroModel 中增加 subscriptions
subscriptions: {
        setup({ dispatch, history }) {
            return history.listen(({ pathname, query }) => {
                if (pathname === '/hero') {
                    dispatch({
                        type: 'fetch'
                    })
                }
            });
        }
    },

这里需要注意的是,subscriptions 是一个全局的监听,就是说,当设定触发条件满足时,所有的 subscriptions 都会响应,所以我们在这里判断了路由为当前路由时,发起一个 effects 事件。

proxy 请求代理

在这里插入图片描述

之所以会出现跨域访问问题,是因为浏览器的安全策略。所以我们预想是不是有一种方式,能够绕过浏览器的安全策略?

那就是先请求一个同源服务器,再由服务器去请求其他的服务器。比如:

  • 我们本来是要请求 https://pvp.qq.com 服务器,但是它存在跨域。
  • 所以我们先请求了 http://localhost:3000 (假设的),它不存在跨域问题,所以它受理了我们的请求,并且我们可以取得它返回的数据。
  • 而由 http://localhost:3000 返回的数据,又是从真实的 https://pvp.qq.com 获取来的,因为服务端不是在浏览器环境,所以就没有浏览器的安全策略问题。
  • 因为 http://localhost:3000 (假设的)这个服务器,它只是把我们请求的参数,转发到真实服务端,又把真实服务端下发的数据,转发给我们,所以我们称它为代理。
    umi 提供了 proxy 来处理这个问题。
配置 proxy
export default {
  plugins: [
    ...
  ],
  "proxy": {
    "/api": {                                       ---step1
      "target": "https://pvp.qq.com", ---step2
      "changeOrigin": true,                         ---step3
      "pathRewrite": { "^/api" : "" }               ---step4
    }
  }
}
  • step1 设置了需要代理的请求头,比如这里定义了 /api ,当你访问如 /api/abc 这样子的请求,就会触发代理
  • step2 设置代理的目标,即真实的服务器地址
  • changeOrigin 设置是否跨域请求资源
  • pathRewrite 表示是否重写请求地址,比如这里的配置,就是把 /api 替换成空字符

src/app.ts

import { ResponseError } from 'umi-request';

export const request = {
-  prefix: 'https://pvp.qq.com',
+  prefix: '/api',
  errorHandler: (error: ResponseError) => {
    // 集中处理错误
    console.log(error);
  },
};

=========================================================================

本文简单学习了umi,更深层次学习还需要大家查看官网配置插件部分。如有不足,还望指出。

参考文档:
umijs官网
yuque umijs

是的,你可以使用 `@connect` 装饰器将两个组件连接到一个 dva model 中。 在 `umi` 中,你也可以使用 `connect` 函数将组件连接到 dva model 中。 具体来说,你可以在两个组件中都使用 `@connect` 装饰器,并将它们连接到同一个 dva model 中。 例如: ```jsx // ComponentA.js import { connect } from 'umi'; import React from 'react'; @connect(({ myModel }) => ({ data: myModel.data, })) class ComponentA extends React.Component { // ... } export default ComponentA; // ComponentB.js import { connect } from 'umi'; import React from 'react'; @connect(({ myModel }) => ({ data: myModel.data, })) class ComponentB extends React.Component { // ... } export default ComponentB; ``` 在上面的示例中,我们将 `ComponentA` 和 `ComponentB` 连接到 `myModel` model 中。 它们都可以访问 `myModel` 中的 `data` 属性。 如果你不想使用装饰器,你也可以使用 `connect` 函数将组件连接到 dva model 中。 例如: ```jsx // ComponentA.js import { connect } from 'umi'; import React from 'react'; class ComponentA extends React.Component { // ... } export default connect(({ myModel }) => ({ data: myModel.data, }))(ComponentA); // ComponentB.js import { connect } from 'umi'; import React from 'react'; class ComponentB extends React.Component { // ... } export default connect(({ myModel }) => ({ data: myModel.data, }))(ComponentB); ``` 这个示例中,我们使用 `connect` 函数将 `ComponentA` 和 `ComponentB` 连接到 `myModel` model 中。 它们都可以访问 `myModel` 中的 `data` 属性。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值