路由
在 Umi 应用是单页应用,页面地址的跳转都是在浏览器端完成的,不会重新请求服务端获取 html,html 只在应用初始化时加载一次。所有页面由不同的组件构成,页面的切换其实就是不同组件的切换,你只需要在配置中把不同的路由路径和对应的组件关联上。
路由类型配置
请参考 history 配置。
配置路由
在配置文件中通过 routes
进行配置,格式为路由信息的数组。
比如:
// .umirc.tsexport default {routes: [{ path: '/', component: 'index' },{ path: '/user', component: 'user' },],}
Umi 4 默认按页拆包,从而有更快的页面加载速度,由于加载过程是异步的,所以往往你需要编写 loading.tsx
来给项目添加加载样式,提升用户体验。
💡
你可以在 Chrome Devtools > 网络 Tab 中将网络设置成低速,然后切换路由查看加载组件是否生效。
path
- Type:
string
path
只支持两种占位符配置,第一种是动态参数 :id
的形式,第二种是 *
通配符,通配符只能出现路由字符串的最后。
✅ 以下是目前支持的路由路径配置形式:
/groups/groups/admin/users/:id/users/:id/messages/files/*/files/:id/*
❌ 以下是目前不支持的路由路径配置形式:
/users/:id?/tweets/:id(\d+)/files/*/cat.jpg/files-*
component
- Type:
string
配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径。如果是相对路径,会从 src/pages
开始寻找。
如果指向 src
目录的文件,可以用 @
,比如 component: '@/layouts/basic'
,推荐使用 @
组织路由文件位置。
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
中,通过 <Outlet/>
来渲染子路由:
import { Outlet } from 'umi'export default function Page() {return (<div style={{ padding: 20 }}><Outlet/></div>)}
这样,访问 /list
和 /admin
就会带上 src/layouts/index
这个 layout 组件。
redirect
- Type:
string
配置路由跳转。
比如:
export default {routes: [{ path: '/', redirect: '/list' },{ path: '/list', component: 'list' },],}
访问 /
会跳转到 /list
。
重定向时,默认不会携带原 url 的查询参数,如需保持原参数,添加 keepQuery
选项即可:
routes: [{ path: '/', redirect: '/list', keepQuery: true },// 注:若你需在跳转时处理参数,可以自行实现一个跳转组件]
wrappers
- Type:
string[]
配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验:
export default {routes: [{ path: '/user', component: 'user',wrappers: ['@/wrappers/auth',],},{ path: '/login', component: 'login' },]}
然后在 src/wrappers/auth
中,
import { Navigate, Outlet } from 'umi'export default (props) => {const { isLogin } = useAuth();if (isLogin) {return <Outlet />;} else{return <Navigate to="/login" />;}}
这样,访问 /user
,就通过 auth
组件做权限校验,如果通过,渲染 src/pages/user
,否则跳转到 /login
。
🚨
wrappers
中的每个组件会给当前的路由组件增加一层嵌套路由,如果你希望路由结构不发生变化,推荐使用高阶组件。先在高阶组件中实现 wrapper 中的逻辑,然后使用该高阶组件装饰对应的路由组件。
举例:
// src/hocs/withAuth.tsximport { Navigate } from 'umi'const withAuth = (Component) => ()=>{const { isLogin } = useAuth();if (isLogin) {return <Component />;} else{return <Navigate to="/login" />;}}
// src/pages/user.tsxconst TheOldPage = ()=>{// ...}export default withAuth(TheOldPage)
layout
- Type:
boolean
通过配置 layout: false
可以单独关闭某一个路由的全局布局:
// .umirc.tsexport default {routes: [// 取消 login 页面的全局布局,从而自行实现整个页面{ path: '/login', component: '@/pages/Login', layout: false },],}
注:
全局布局可能来自于
layouts/index.tsx
约定,或插件添加的 layout(如@umijs/max
自带的 layout 插件将自动添加菜单布局),当配置layout: false
时,将取消所有 layout ,此时组件内容占据整个页面,多用于登录页等场景。layout: false
仅对一级路由生效,更多例子详见 全局 layout 。
约定式路由
除配置式路由外,Umi 也支持约定式路由。约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
如果没有 routes 配置,Umi 会进入约定式路由模式,然后分析 src/pages
目录拿到路由配置。
比如以下文件结构:
.└── pages├── index.tsx└── users.tsx
会得到以下路由配置,
[{ path: '/', component: '@/pages/index' },{ path: '/users', component: '@/pages/users' },]
使用约定式路由时,约定
src/pages
下所有的(j|t)sx?
文件即路由。如果你需要修改默认规则,可以使用 conventionRoutes 配置。
动态路由
约定,带 $
前缀的目录或文件为动态路由。若 $
后不指定参数名,则代表 *
通配,比如以下目录结构:
比如:
src/pages/users/$id.tsx
会成为/users/:id
src/pages/users/$id/settings.tsx
会成为/users/:id/settings
举个完整的例子,比如以下文件结构,
+ pages/+ foo/- $slug.tsx+ $bar/- $.tsx- index.tsx
会生成路由配置如下:
[{ path: '/', component: '@/pages/index.tsx' },{ path: '/foo/:slug', component: '@/pages/foo/$slug.tsx' },{ path: '/:bar/*', component: '@/pages/$bar/$.tsx' },];
全局 layout
约定 src/layouts/index.tsx
为全局路由。返回一个 React 组件,并通过 <Outlet />
渲染嵌套路由。
如以下目录结构:
.└── src├── layouts│ └── index.tsx└── pages├── index.tsx└── users.tsx
会生成如下路由:
[{path: '/',component: '@/layouts/index',routes: [{ path: '', component: '@/pages/index' },{ path: 'users', component: '@/pages/users' },],},]
可以通过 layout: false
来细粒度关闭某个路由的 全局布局 显示,该选项只在一级生效:
routes: [{path: '/',component: './index',// 🟢layout: false},{path: '/users',routes: [// 🔴 不生效,此时该路由的 layout 并不是全局布局,而是 `/users`{ layout: false }]}]
一个自定义的全局 layout
格式如下:
import { Outlet } from 'umi'export default function Layout() {return <Outlet />}
不同的全局 layout
你可能需要针对不同路由输出不同的全局 layout,Umi 不支持这样的配置,但你仍可以在 src/layouts/index.tsx
中对 location.path
做区分,渲染不同的 layout 。
比如想要针对 /login
输出简单布局,
import { useLocation, Outlet } from 'umi';export default function() {const location = useLocation();if (location.pathname === '/login') {return <SimpleLayout><Outlet /></SimpleLayout>}// 使用 `useAppData` / `useSelectedRoutes` 可以获得更多路由信息// const { clientRoutes } = useAppData()// const routes = useSelectedRoutes()return (<><Header /><Outlet /><Footer /></>);}
404 路由
约定 src/pages/404.tsx
为 404 页面,需返回 React 组件。
比如以下目录结构,
.└── pages├── 404.tsx├── index.tsx└── users.tsx
会生成路由,
[{ path: '/', component: '@/pages/index' },{ path: '/users', component: '@/pages/users' },{ path: '/*', component: '@/pages/404' },]
这样,如果访问 /foo
,则 /
和 /users
都不能匹配,于是会 fallback 到 404 路由,通过 src/pages/404.tsx
进行渲染。
404 只有约定式路由会自动生效,如果使用配置式路由,需要自行配置 404 的通配路由。
页面跳转
命令式跳转请使用 history
API
组件内还可以使用 useNavigate
hook
Link 组件
比如:
import { Link } from 'umi';export default function Page() {return (<div><Link to="/users">Users Page</Link></div>)}
然后点击 Users Page
就会跳转到 /users
地址。
注意:
Link
只用于单页应用的内部跳转,如果是外部地址跳转请使用a
标签
路由组件参数
Umi 4 使用 react-router@6 作为路由组件,路由参数的获取使其 hooks。
match 信息
const match = useMatch('/comp/:id')// match{"params": {"id": "paramId"},"pathname": "/comp/paramId/","pathnameBase": "/comp/paramId","pattern": {"path": "/comp/:id","caseSensitive": false,"end": true}}
location 信息
const location = useLocation();// location{"pathname": "/path/","search": "","hash": "","state": null,"key": "default"}
🚨
推荐使用 useLocation
, 而不是直接访问 history.location
. 两者的区别是 pathname
的部分。
history.location.pathname
是完整的浏览器的路径名;而 useLocation
中返回的 pathname
是相对项目配置的base
的路径。
举例:项目如果配置 base: '/testbase'
,
当前浏览器地址为 https://localhost:8000/testbase/page/apple
history.location.pathname
为 /testbase/page/apple
useLocation().pathname
为 /page/apple
路由动态参数
// 路由配置 /comp/:id// 当前 location /comp/paramIdconst params = useParams();// params{"id": "paramId"}
query 信息
// 当前 location /comp?a=b;const [searchParams, setSearchParams] = useSearchParams();searchParams.get('a') // bsearchParams.toString() // a=bsetSearchParams({a:'c',d:'e'}) // location 变成 /comp?a=c&d=e
searchParams
的 api 参考