跳到主要内容

路由

ice.js 采用 约定式路由,并针对 嵌套路由 做了一系列加载和渲染上的优化,以构建出性能更好的 Web 应用。

基础概念

约定式路由

框架会根据项目的目录结构自动生成应用的路由信息。src/pages 目录下的每一个 .(js|jsx|tsx) 文件会被映射为一个路由地址,示例如下:

小程序端路由规则

对于小程序来说,使用约定式路由会带来无法确定首页的问题(在原生小程序中,app.jsonpages 数组的第一项即被指定为首页)。因此 ice.js 开发小程序时,用户需要在 src/app.tsx 中通过导出 miniappManifest 进行路由的指定,示例如下:

export const miniappManifest = {
routes: [
'index',
'about',
'repo/index',
'repo/preview',
],
};

注意,routes 中的每一项应该与文件在 pages 目录下的实际路径保持一致,且其第一项将作为小程序的首页被加载。

路由组件

路由组件,是每一个页面的入口文件,通过 export default 导出其具体实现,例如:

src/pages/index.tsx
export default function Home() {
return (
<div>Hello ICE</div>
);
};

路由组件支持配置页面级信息和数据加载逻辑,详见页面

布局组件

警告

小程序端不支持。

pages 目录下,还可以创建一类特殊的组件,来维护全局或一组页面共用的布局, 其文件名约定为 layout.(js|jsx|tsx)

布局组件和路由组件一样,也通过 export default 导出其具体实现。

import { Outlet } from 'ice';

export default function Layout() {
return (
<div>
<h1>Root Layout</h1>
<h2>Hello ICE</h2>
<Outlet />
</div>
)
}

其中, <Outlet /> 组件对应需要被布局组件嵌套的子组件。

布局组件:

  • 如果位于 pages 目录的最顶层,则它将作为全局布局,嵌套在所有路由组件外。
  • 如果位于某个子文件夹,则它将作为页面级布局,嵌套在这个目录下的其他路由组件外。

如果同时存在 全局布局组件页面级布局组件,则全局布局组件会嵌套于页面级布局组件之外。

嵌套路由

通过创建文件夹布局组件,可以轻松构建嵌套路由。例如,下面的示例中,/repo/preview 页面,由这三个组件嵌套而成:

  • layout.tsx
  • repo/layout.tsx
  • repo/preview.tsx

ice.js 针对嵌套路由的场景,应用了以下优化,来让页面达成更好的性能体验:

  • 各路由组件的资源数据请求会被并行加载,以达到最快的资源加载速度。
  • 路由间跳转,比如从 /repo/preview 跳转到 /repo/edit,框架只会加载差异化的路由组件 edit.tsx 进行渲染,而不会重新渲染整个页面。

利用框架对嵌套路由所做的优化,我们可以将页面中逻辑相对分离的部分,用嵌套路由的方式来组织,以获得更好的加载体验。

例如,下面这个常见的移动端营销页,可以将顶部通用的 Slider 抽象为布局组件,将不同 tab 下对应的瀑布流,抽象为路由组件。这样,Slider 和瀑布流就可以做到并行加载,并且当切换 tab 时,新的 tab 内容将由框架触发按需加载和渲染。示例工程

提示

假如同时存在 src/pages/home.tsxsrc/pages/home/index.tsx,则访问 /home 路由地址时,只有 src/pages/home/index.tsx 组件渲染。

如果你想有嵌套路由,但是又不想创建有嵌套目录结构,你可以使用 . 来创建一个扁平的文件名。

└── src
├── root.jsx
└── pages
- ├── about
- │ ├── repo
- │ │ └── $id.tsx
│ └── index.tsx
+ └── about.repo.$id.tsx

这样,我们就可以通过 /about/repo/$id 的路由地址访问到 about.repo.$id.tsx 的路由组件了。

动态路由

警告

小程序端不支持。

在某些场景下可能需要动态指定路由,例如 /user/:id,可以以 $ 开头创建文件名或目录名,比如 src/pages/user/$id.tsx

通配路由

src/pages 目录下的 $.tsx 文件将会被解析成通配路由。如果当前访问的路由没有任何组件能匹配,将会渲染通配路由组件。

通常可以增加 src/pages/$.tsx 作为自定义 404 页面。

转义路由

默认情况下,对于 src/pages/**/index.tsx 这样的路由文件,路由的生成规则是这样的:

路由文件路由
src/pages/index.tsx/
src/pages/about/index.tsx/about

可以看到,index 字符串不会出现在路由上,被转成 /。如果希望路由上保留 /index,可以使用转义字符 []

路由文件路由
src/pages/[index].tsx/index
src/pages/about/[index].tsx/about/index

路由跳转

ice.js 提供三种方式进行路由间跳转,这样就可以只加载下一个页面相比于当前页面差异化的 Bundle 进行渲染,以达到更好的性能体验。

history

可使用 history API 进行路由跳转。

import { history } from 'ice';

export default () => {
history.push('/dashboard');
}

useNavigate

组件内可以使用 useNavigate Hook 进行路由跳转。

import { useNavigate } from 'ice';

export default () => {
const navigate = useNavigate();
navigate('/logout');
}

组件内可以使用 <Link /> 组件进行路由跳转。

src/pages/index.tsx
import { Link } from 'ice';

export default function Home() {
return (
<>
<div>Hello ICE</div>
<Link to="/about">about ice</Link>
</>
);
}
信息

在小程序中,Link 组件底层实现即为原生 navigator 组件。

获取路由信息

location

使用 useLocation 获取 location 信息。

import { useLocation } from 'ice';

export default function () {
const location = useLocation();
}

query

使用 useSearchParams 获取和修改 query 信息。

import { useSearchParams } from 'ice';

export default function Repo() {
const [searchParams, setSearchParams] = useSearchParams();
console.log(searchParams);
setSearchParams({ tab: 'a' })
}

动态路由参数

在动态路由组件使用 useParams 获取当前路由的参数。

import { useParams } from 'ice';

// 路由规则为 /repo/:id
// 当前路径 /repo/123
export default function Repo() {
const params = useParams();
console.log(params);
// { id: 123 }
}

忽略被解析为路由组件

默认情况下,ice.js 会把 src/pages 目录下的每一个 .(js|jsx|tsx) 文件映射为一个路由地址。如果你有一些组件不想被解析成路由组件,可通过 ignoreFiles 进行配置。

ice.config.mts
import { defineConfig } from '@ice/app';

export default defineConfig({
routes: {
ignoreFiles: [
'custom.tsx',
'**/components/**', // 如果每个页面下有 components 目录存放当前页面的组件,可以通过添加此配置忽略被解析成路由组件
],
},
});

定制路由地址

对于约定式路由不满足的场景,可以通过 defineRoutes 方式进行自定义。

ice.config.mts
import { defineConfig } from '@ice/app';

export default defineConfig({
routes: {
defineRoutes: (route) => {
// 将 /hello 路由访问内容指定为 about.tsx
route('/hello', 'about.tsx');
},
},
});