路由有三种方式,分别是,约定式路由(即文件路由,根据文件名自动配置)、编译时配置式路由(config/config.js中配置)和运行时配置式路由(src/app.js中配置)
注意:若 .umirc.(ts|js) 或 config/config.(ts|js) 文件中对 router 进行了配置,约定式路由将失效、新添的页面不会自动被 umi 编译,umi 将使用编译时配置式路由
约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置
- (1)基础路由: 基础路由,就是自动生成的路由。生成的规则是,pages下面的直接子文件作为pages后面的第一级路由,里面的index.js为斜杠
/
路径。如下
// 假设 pages 目录结构如下:
+ pages/
+ users/
- index.js
- list.js
- index.js
// 那么,umi 会自动生成路由配置如下:
[{ path: '/', component: './pages/index.js' },
{ path: '/users/', component: './pages/users/index.js' },
{ path: '/users/list', component: './pages/users/list.js' },]
- (2)动态路由: umi 里约定,带 $ 前缀的目录或文件为动态路由。对象路由里面动态路由用
:
表示
// 假设 pages 目录结构如下:
+ pages/
+ $post/
- index.js
- comments.js
+ users/
$id.js
- index.js
// 那么,umi 会自动生成路由配置如下:
[{ path: '/', component: './pages/index.js' },
{ path: '/users/:id', component: './pages/users/$id.js' },
{ path: '/:post/', component: './pages/$post/index.js' },
{ path: '/:post/comments', component: './pages/$post/comments.js' },]
- (3)可选的动态路由: umi 里约定动态路由如果带 $ 后缀,则为可选动态路由。对象路由里面可选路由用
?
表示
// 假设 pages 目录结构如下:
+ pages/
+ users/
- $id$.js
- index.js
// 那么,umi 会自动生成路由配置如下:
[{ path: '/': component: './pages/index.js' },
{ path: '/users/:id?': component: './pages/users/$id$.js' },]
- (4)嵌套路由: umi 里约定目录下有 _layout.js 时会生成嵌套路由,以 _layout.js 为该目录的 layout。对象路由里面嵌套路由写在
routes
里面
// 假设 pages 目录结构如下:
+ pages/
+ users/
- _layout.js
- $id.js
- index.js
// 那么,umi 会自动生成路由配置如下:
[{ path: '/users', component: './pages/users/_layout.js',
routes: [
{ path: '/users/', component: './pages/users/index.js' },
{ path: '/users/:id', component: './pages/users/$id.js' },],},]
编译时配置式路由,可以配置 .umirc.(ts|js) 或者config/config.(ts|js) 配置文件中的 routes 属性;如果配置了这个文件,则会使约定式路由无效,即不会对 src/pages 目录做约定式的解析
- (1)路由配置:config.js文件里面导出的routes属性为一个数组,数组的每一项为一个路由对象,路由对象的参数如下:
- <1>path参数: 这个就为一个路由路径。默认情况下,访问子路由的时候,会把父路由的所有组件也同时加载出来。 类型:string
- 如,一个路由
{ path: '/', component: '@/layouts/basic',routes:[{path: '/user',component:'./content/content'}] }
- 这时候如果输入路由
/user
, 页面中会同时加载组件basic和组件content- <2>component参数: 这个是和前面的路由路径对应的组件路径,即切换到对应的路由,就会显示这个对应的组件。component 默认是相对于 src/pages 目录的。但是也可以用@来进行指定,@目录依然代表src目录。 类型:string
- <3>routes参数: 表示子路由的所有配置,里面的依然是所有的路由对象
- <4>redirect参数: 表示重定向路由。值为一个path路径。即,访问该路由的时候,重定向到指定路由
{ exact: true, path: '/', redirect: '/list' }
。即访问路由/
的时候,重定向到路由/list
- <5>exact参数: 表示是否精准匹配。加了这个属性,访问子路由的时候,就不会把父路由的组件也同时加载出来了,而只会加载子路由的组件。 类型:boolean,默认值:false
- 如,一个路由
{ path: '/', component: '@/layouts/basic',routes:[{path: '/user',component:'./content/content',exact: true}] }
- 这时候如果输入路由
/user
, 页面中只会加载content组件- <6>title参数:配置路由的标题
- (2)路由组件参数: 简单说就是,在组件的props上面可以获取到的参数
- match,当前路由和 url match 后的对象,包含 params(这个params就是动态路由的时候,获取传入的那个值)、path、url 和 isExact 属性
- location,表示应用当前出于哪个位置,包含 pathname、search、query 等属性。这个参数没用,直接用window.location,就和这个是一样的了
- history,同 api#history 接口
- route,当前路由配置,包含 path、exact、component、routes 等
// 下面只是配置的默认导出的一个routers、plugins属性,实际上还可以有其它属性的
export default {
routes: [
{ path: '/', component: '@/layouts/basic' }, // 这里就是用@来指定目录
{ path: '/list', component: './b',redirect: '/users'},
{ path: '/users', component: './users/_layout',
routes: [{ path: '/users/detail', component: './users/detail' },
{ path: '/users/:id', component: './users/id' }]},
],
plugins: [['umi-plugin-react',{dva: {immer: true}}],]
};
umi 的权限路由是通过编译时配置式路由的 Routes 属性来实现。对应Authorized.js文件,指定的权限为下面authority里面的那个
{
path: '/',
component: '../layouts/BasicLayout',
Routes: ['src/pages/Authorized'],
authority: ['admin', 'user'],
}
这里这个动效用的是
react-transition-group
,这个用的时候再来看
umi 默认是用的 Browser History,如果要用 Hash History,需在config/config.js中配置
export default {history: 'hash',}
与运行时配置相关的还有一个叫,编译时配置式路由,就是指config/config.js里面的配置,运行时配置式路由,就是指src 目录下的 app.js 中的路由配置。这个是很重要的
这个就相当于是在配置vue中的全局的钩子函数。即组件里面在执行一些操作的时候,就会先触发这里面的一些函数
下面这些函数,都是写在app.js里面的。并且都需要进行命名空间导出,才有效果
- (1)patchRoutes()函数: 左右这个函数是用来修改路由当前页面中的路由的,参数如下
- routes:
Array
,config/config.js
中的全部路由配置,修改了这个routes参数,就相当于修改了全局的路由配置,- 使用场景:<1>和 render 配合使用,请求服务端根据响应动态更新路由。<2>修改全部路由,加上某个前缀
// 这个在最前面添加一个 /foo 路由
export function patchRoutes(routes) {
routes[0].unshift({
path: '/foo',
component: require('./routes/foo').default,
});
}
// 这个就为根据服务端的请求,动态更新路由
let extraRoutes;
export function patchRoutes({ routes }) {
merge(routes, extraRoutes);
}
export function render() {
fetch('/api/routes').then((res) => { extraRoutes = res.routes })
}
- (2)render()函数: 用于这个就是在组件里面的render生命周期执行之前调用,参数如下
- oldRender:
Function
,原始组件被捕获之前应该要执行的render 方法,需至少被调用一次- 使用场景:渲染应用之前做权限校验,不通过则跳转到登录页
export function render(oldRender) {
getLoginStatus(); // 这个为登陆验证的方法
oldRender();
}
// 判断是否进行了登陆
import { history } from 'umi';
export function render(oldRender) {
fetch('/api/auth').then(auth => {
if (auth.isLogin) { oldRender() } // 如果登陆了,就执行 组件里面应该执行的render方法
else { history.push('/login'); } // 没有登陆,就跳转
});
}
- (3)onRouteChange()函数: 用于在初始加载和路由切换时触发,参数如下
- location:
Object
,history 提供的 location 对象- routes:
Array
,路由配置- action:
PUSH|POP|REPLACE|undefined
,初次加载时为 undefined- 使用场景:埋点统计。初次加载时也会执行,但 action 为 undefined
export function onRouteChange({ location, routes, action }) {
bacon(location.pathname); // 比如bacon为一个进行埋点统计的路由,传入的参数为需要去到的路由页面
}
- (4)rootContainer: 用于封装 root container,可以取一部分,或者外面套一层,等等,参数如下
- container:
ReactComponent
,React 应用最上层的根组件- 使用场景:dva、intl 等需要在外层有个 Provider 的场景
export function rootContainer(container) {
const DvaContainer = require('@tmp/DvaContainer').default;
return React.createElement(DvaContainer, null, container);
}
- (5)modifyRouteProps: 用于修改传给路由组件的 props,参数如下
- props:
Object
,原始 props- Object:
里面有个route属性
,表示当前路由配置- 使用场景:需返回新的 props
export function modifyRouteProps(props, { route }) {
return { ...props, foo: 'bar' };
}
在 umi 里,页面之间跳转有两种方式:标签跳转和函数跳转
- 标签跳转: 基于
umi/link
,通常作为 React 组件使用。就是说用<link>
标签进行跳转
import Link from 'umi/link';
export default () => (<Link to="/list">Go to list page</Link>);
- 函数跳转: 基于 umi/router,通常在事件处理中被调用。就是说函数式跳转,一般不用这个,在umi 3.0中已经被废弃,而是用下面那个history跳转。更多跳转方法,见 https://umijs.org/zh/api/#umi-router
import router from 'umi/router';
function goToListPage() {router.push('/list');}
原生的react-router实现跳转。使用这个history的时候,有时候会报错 history为undefined,所以这个时候需要用
withRouter()()
这个高阶组件来包裹当前组件
- props.history.push(params): 这个push()函数的params参数,可以为一个字符串也可以为一个对象。
- 参数为字符串:为字符串的时候,就代表要跳转的目标路由路径
- 参数为对象:为对象的时候,就可以 给目标路由传参了
- pathname属性:表示要跳转的目标路由路径。 类型:string
- query属性:表示要传给目标路由的参数。不过参数会被放到地址栏。 类型:object
- state属性:也是传给目标路由的参数。参数是加密的,不会放到地址栏。 类型:object
- 组件中获取传过来的路由参数:如果是query传过来的对象参数,就用
this.props.location.query
(默认为{}); 如果是state传过来的对象参数,就用this.props.location.state
(默认为undefined);
this.props.history.push('/user/list')
// query传参
this.props.history.push({ pathname : '/sort' ,query : { name: ' sunny'} })
this.props.location.query.name; // 接收参数
// state传参
this.props.history.push({ pathname:'/sort',state:{name : 'sunny' } })
this.props.location.state.name; // 接收参数
// 组件中使用this.props.history
import React, { Component } from 'react';
import { connect } from 'dva';
import {withRouter} from "react-router-dom";
class Test extends Component {
func(pro) {this.props.history.push({pathname: '/Demo', state: {my: 1111, my2: 2222}})} // 这个时候就可以访问到this.props.history了
render() {return (<><div onClick={()=>{this.func();}}></div></>);}
}
export default connect(()=>{return {};})(withRouter(Test));
// 另外一个组件中接收传过来的路由参数
class Demo extends Component {
componentDidMount() {this.props.location.state;} // 输出为{my: 1111, my2: 2222}。如果不是从上面的组件点击跳转的,那么this.props.location.state的值为undefined
}
export default Demo;
- window的location对象改变路由实现跳转:
location.pathname = '/user/list'
,这种方法可以是可以,不过就是切换路由的速度太慢了。在可以用上面方法的情况下,不推荐使用
这个主要就是一些文件在umi中会被自动处理,如下:
.js, .jsx, .mjs, .jsx, .json: 由 babel-loader 处理
.ts: 由 ts-loader 处理
.graphql, .gql: 由 graphql-tag/loader 处理
.css, .less, .sass: 由 css-loader, postcss-loader, less-loader 处理
.svg: 由 @svgr/core 处理。使用 umi,你可以用如下方式引入 svg
import { ReactComponent as Avatar } from './avatar.svg'
function Example() {return <Avatar />}