路由方案

icejs 提供了 配置式路由约定式路由 两种方案,默认推荐配置式路由方案,开发者可根据偏好选择。

当项目中存在 src/routes.[ts|js] 文件时,则使用配置式路由,否则使用约定式路由。

配置式路由

路由按照标准协议进行配置,用来描述路由的结构关系,路由配置协议如下:

  1. // src/routes.ts
  2. import UserLayout from '@/Layouts/UserLayout';
  3. import UserLogin from '@/pages/UserLogin';
  4. import NotFound from '@/components/NotFound';
  5. const routerConfig = [
  6. // 分组路由,children 里的路由会将父节点的 component 作为布局组件
  7. {
  8. path: '/user',
  9. component: UserLayout,
  10. children: [
  11. {
  12. path: '/login',
  13. exact: true,
  14. component: UserLogin,
  15. },
  16. {
  17. path: '/',
  18. // 重定向
  19. redirect: '/user/login',
  20. },
  21. {
  22. // 404 没有匹配到的路由
  23. component: NotFound,
  24. },
  25. ],
  26. },
  27. // 非分组路由
  28. {
  29. path: '/about',
  30. component: About,
  31. },
  32. ];
  33. export default routerConfig;

注意:路由有一个按顺序匹配的规则,从上到下一旦命中路由匹配规则就会停止遍历,因此如果你在最前面配置了 / 这样一个路由,则所有的路由都会命中该规则,导致其他路由没有效果,所以在开发时要注意路由的顺序以及 exact 属性的使用。

约定式路由

顾名思义,约定式路由会根据项目的目录结构自动生成路由配置,无需开发者手动配置。约定式路由虽然不需要用户手动配置,但因为路由配置本身存在很多规则,因此约定式路由也需要约定一系列的目录结构设计,这本身也有一定的学习成本。

基础路由

假设 pages 的目录结构如下:

  1. src/pages
  2. └── About
  3. └── index.tsx
  4. └── Dashboard
  5. ├── a.tsx
  6. └── b.tsx

那么,框架自动生成的路由配置如下:

  1. export default [
  2. {
  3. path: '/about',
  4. exact: true,
  5. component: PageAbout
  6. },
  7. {
  8. path: '/dashboard/a',
  9. exact: true,
  10. component: PageDashboardA
  11. },
  12. {
  13. path: '/dashboard/b',
  14. exact: true,
  15. component: PageDashboardB
  16. }
  17. ]

404 路由

如果 src/pages/404.[jsx|tsx] 或者 src/pages/404/index.[jsx|tsx] 文件存在,则将它作为 404 页面

index 路由

如果 src/pages/index.[jsx|tsx] 文件存在,则会自动映射到 / 的路由。

嵌套路由

框架约定页面目录下存在名为 _layout.[jsx|tsx] 时,会产生一个嵌套路由,当前目录和子目录均为子路由。

假设 pages 的目录结构如下:

  1. src/pages
  2. └── About
  3. ├── _layout.tsx
  4. ├── a.tsx
  5. └── b.tsx
  6. └── Dashboard
  7. └── index.tsx

那么,框架自动生成的路由配置如下:

  1. [
  2. {
  3. path: '/about',
  4. exact: false,
  5. component: LayoutAbout,
  6. children: [
  7. {
  8. path: '/a',
  9. exact: true,
  10. component: PageAboutA
  11. },
  12. {
  13. path: '/b',
  14. exact: true,
  15. component: PageAboutB
  16. },
  17. ],
  18. },
  19. {
  20. path: '/dashboard',
  21. exact: true,
  22. component: PageDashboard
  23. }
  24. ]

动态路由

框架约定定义带参数的动态路由,需要创建对应的以下划线作为前缀的文件或目录。

  • 路径中 $ 作为文件夹或文件名的首个字符,标识一个动态路由,如 src/pages/app/$uid.tsx 会生成路由 /app/:uid
  • 路径中文件夹或文件名的首个和最后一个字符同时为 $,标识一个可选的动态路由,如 src/pages/app/$uid$.tsx 会生成路由 /app/:uid?

假设 pages 的目录结构如下:

  1. src/pages
  2. └── repo
  3. ├── $pid.tsx
  4. └── index.tsx
  5. └── $uid$.tsx

那么,框架自动生成的路由配置如下:

  1. export default [
  2. {
  3. path: '/repo/:pid',
  4. exact: true,
  5. component: PageRepo$pid
  6. },
  7. {
  8. path: '/repo',
  9. exact: true,
  10. component: PageRepo
  11. },
  12. {
  13. path: '/:uid?',
  14. exact: true,
  15. component: Page$uid$
  16. }
  17. ]

全局 Layout

如果 src/layouts/index.[jsx|tsx] 文件存在,则将它默认作为全局 layout

路由配置

运行时配置

src/app.ts 中,我们可以配置路由的类型和基础路径等路由信息,具体配置如下:

  1. import { createApp } from 'ice';
  2. const appConfig = {
  3. router: {
  4. type: 'browser',
  5. basename: '/seller',
  6. fallback: <div>loading...</div>
  7. modifyRoutes: (routes) => {
  8. return routes;
  9. }
  10. }
  11. };
  12. createApp(appConfig);

options:

  • type: 路由类型,默认值 hash,可选值 browser|hash|static
  • basename: 路由基准地址
  • fallback: 开启按需加载时配置 fallback UI
  • modifyRoutes: 动态修改路由

构建配置

build.json 中,我们也可以自定义一些构建配置:

  1. {
  2. "router": {
  3. // ...options
  4. }
  5. }

options:

  • configPath: 仅配置式路由,类型 string,默认值 'src/routes.[t|j]s',自定义配置式路由文件的地址
  • caseSensitive: 仅约定式路由,类型 boolean,默认值 false, 根据文件名转换为路由时是否大小写敏感
  • ignoreRoutes: 仅约定式路由,类型 string[],默认值 [],忽略指定路由的生成
  • ignorePaths: 仅约定式路由,类型 string[],默认值 ['components'],生成路由时忽略指定目录

按需加载

配置式

在配置式路由中如果需要开启按需加载,只需要在路由文件中通过 lazy 方法引入组件即可:

  1. // src/routes.ts
  2. + import { lazy } from 'ice';
  3. - import UserLogin from '@/pages/UserLogin';
  4. + const UserLogin = lazy(() => import('@/pages/UserLogin'));
  5. const routerConfig = [
  6. {
  7. path: '/login',
  8. component: UserLogin,
  9. },
  10. ]

约定式

在约定式路由中如果需要开启按需加载,只需要在 build.json 中的 router 选项配置 lazy 属性即可:

  1. // build.json
  2. {
  3. "router": {
  4. + "lazy": true
  5. }
  6. }

fallback

当组件动态加载过程中或者组件渲染失败时,可以通过 fallback 属性设置提示:

  1. import { createApp } from 'ice';
  2. const appConfig = {
  3. router: {
  4. + fallback: <div>loading...</div>
  5. }
  6. }
  7. createApp(appConfig);

路由 API

icejs 的路由能力基于 react-router,因此你也可以获取到 react-router 支持的其他路由 API:

  1. import {
  2. Link,
  3. useHistory,
  4. useLocation,
  5. useParams,
  6. useRouteMatch,
  7. withRouter,
  8. matchPath,
  9. NavLink,
  10. Prompt,
  11. } from 'ice';

Link

通过 <Link /> 标签组件可实现路由跳转,使用方式:

  1. import { Link } from 'ice';
  2. function Demo() {
  3. return (
  4. <div>
  5. <Link to='/courses?sort=name' />
  6. {/* 可以携带额外的数据 `state` 到路由中。 */}
  7. <Link
  8. to={{
  9. pathname: '/courses',
  10. search: '?sort=name',
  11. hash: '#the-hash',
  12. state: { fromDashboard: true },
  13. }}
  14. />
  15. </div>
  16. )
  17. }

useHistory

useHistory hook 用于获取导航的 history 实例。

  1. import { useHistory } from 'ice';
  2. function HomeButton() {
  3. const history = useHistory();
  4. function handleClick() {
  5. history.push('/home);
  6. }
  7. return (
  8. <button type='button' onClick={handleClick}>
  9. Go home
  10. </button>
  11. );
  12. }

useLocation

useLocation hook 返回代表当前 URL 的 location 对象。可以像 useState 一样使用它,只要 URL 更改,它就会返回一个新位置。

useParams

useParams hook 返回 URL 参数的 key/value 的对象。 使用它来访问当前 的 match.params。

useRouteMatch

useRouteMatch hook 尝试以与 相同的方式匹配当前URL。它主要用于在不实际渲染 的情况下访问匹配数据。

更多使用示例

withRouter

通过 withRouter 方法调用实现跳转;如果调用方法的地方在 React 组件内部,可以直接在组件上添加 withRouter 的装饰器,然后组件内可以通过 props 获取到相关 API:

  1. import React from 'react';
  2. import { withRouter } from 'ice';
  3. function ShowTheLocation(props) {
  4. const { history, location } = props;
  5. const handleHistoryPush = () => {
  6. history.push('/new-path');
  7. };
  8. return (
  9. <div>
  10. <div>当前路径: {location.pathname}</div>
  11. <button onClick={handleHistoryPush}>点击跳转新页面</button>
  12. </div>
  13. );
  14. }
  15. export default withRouter(ShowTheLocation);

matchPath

判断当前 URL 是否匹配。

  1. import { matchPath } from 'ice';
  2. const match = matchPath('/users/123', {
  3. path: '/users/:id',
  4. exact: true,
  5. strict: false
  6. });

NavLink

NavLink 组件的用法和 Link 组件基本相同,区别在于 NavLink 组件匹配时可以添加 active 属性。

  1. <NavLink to='/faq' activeClassName='selected'>
  2. FAQs
  3. </NavLink>

Prompt

在离开页面路由跳转时,自定义拦截组件。

常见问题

路由带着 # 号?

前端路由通常有两种实现方式:HashHistory 和 BrowserHistory,路由都带着 # 说明使用的是 HashHistory。这两种方式优缺点如下:

特点\方案 HashRouter BrowserRouter
美观度 不好,有 # 号
易用性 简单 中等,需要 server 配合
依赖 server 不依赖 依赖
跟锚点功能冲突 冲突 不冲突
兼容性 IE8 IE10

开发者可以根据自己的实际情况选择对应方案。

如何启用 BrowserRouter

本地开发时,只需要在 src/app.ts 中增加以下配置即可:

  1. import { createApp } from 'ice';
  2. const appConfig = {
  3. router: {
  4. + type: 'browser',
  5. }
  6. };
  7. createApp(appConfig);

线上运行时需要服务端支持,否则会出现刷新 404 问题,具体方案请参考社区文档: