路由
ice.js 采用 约定式路由
,并针对 嵌套路由
做了一系列加载和渲染上的优化,以构建出性能更好的 Web 应用。
基础概念
约定式路由
框架会根据项目的目录结构自动生成应用的路由信息。src/pages
目录下的每一个 .(js|jsx|tsx)
文件会被映射为一个路由地址,示例如下:
小程序端路由规则
对于小程序来说,使用约定式路由会带来无法确定首页的问题(在原生小程序中,app.json
中 pages
数组的第一项即被指定为首页)。因此 ice.js 开发小程序时,用户需要在 src/app.tsx 中通过导出 miniappManifest
进行路由的指定,示例如下:
export const miniappManifest = {
routes: [
'index',
'about',
'repo/index',
'repo/preview',
],
};
注意,routes
中的每一项应该与文件在 pages
目录下的实际路径保持一致,且其第一项将作为小程序的首页被加载。
路由组件
路由组件,是每一个页面的入口文件,通过 export default
导出其具体实现,例如:
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.tsx
和 src/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 组件
组件内可以使用 <Link />
组件进行路由跳转。
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 进行配置。
import { defineConfig } from '@ice/app';
export default defineConfig({
routes: {
ignoreFiles: [
'custom.tsx',
'**/components/**', // 如果每个页面下有 components 目录存放当前页面的组件,可以通过添加此配置忽略被解析成路由组件
],
},
});
定制路由地址
对于约定式路由不满足的场景,可以通过 defineRoutes 方式进行自定义。
import { defineConfig } from '@ice/app';
export default defineConfig({
routes: {
defineRoutes: (route) => {
// 将 /hello 路由访问内容指定为 about.tsx
route('/hello', 'about.tsx');
},
},
});