跳转到主文档

使用自控式路由​​​​

上一章节中,我们学习了如何创建应用入口。

这一章节中,我们将会学习如何为入口增加【 客户端路由 】。

我们分别用两种不同的方式,为 contactslanding-page 增加客户端路由逻辑。

contactslanding-page 这两个入口,都是通过 CLI 自动创建出来的,在创建过程中我们没有修改入口的默认配置,因此每个入口的客户端路由都是默认开启的。

modern.config.js
export default defineConfig({
runtime: {
router: true,
state: true,
},
});

之前我们已经为联系人列表增加了 Archive 按钮,接下来我们添加一个客户端路由 /archives,访问这个路由时,只显示已存档的联系人,而原有的 / 继续显示所有联系人。

打开 src/contacts/App.tsx,在原有的 mockData 下方新增 mockArchivedData

const mockData = getAvatar([
{ name: 'Thomas', email: 'w.kccip@bllmfbgv.dm' },
{ name: 'Chow', email: 'f.lfqljnlk@ywoefljhc.af' },
{ name: 'Bradley', email: 'd.wfovsqyo@gpkcjwjgb.fr' },
{ name: 'Davis', email: '"t.kqkoj@utlkwnpwk.nu' },
]);

const mockArchivedData = getAvatar([
{ name: 'Thomas', email: 'w.kccip@bllmfbgv.dm' },
{ name: 'Chow', email: 'f.lfqljnlk@ywoefljhc.af' },
]);

在文件顶部引入 React RouterRouteSwitchReact HelmetHelmet 组件:

import { Route, Switch } from '@modern-js/runtime/router';
import { Helmet } from '@modern-js/runtime/head';

App 组件中使用 Route 写两个路由,分别用不同的 mock 数据渲染列表:

function App() {
return (
<div className="container lg mx-auto">
<Switch>
<Route path="/" exact={true}>
<Helmet>
<title>All</title>
</Helmet>
<List
dataSource={mockData}
renderItem={info => <Item key={info.name} info={info} />}
/>
</Route>
<Route path="/archives" exact={true}>
<Helmet>
<title>Archives</title>
</Helmet>
<List
dataSource={mockArchivedData}
renderItem={info => <Item key={info.name} info={info} />}
/>
</Route>
</Switch>
</div>
);
}

Modern.js 默认集成了 react-helmet,无需安装依赖,可以直接使用,也可以结合 SSR 使用,满足 SEO 需求。

Modern.js 也默认集成了 react-router,无需安装依赖和自己配置 BrowserRouter 等样板代码,可以直接用 Route、Switch 等组件实现路由逻辑。

React Router v4+ 有两种用法,一种是 component-based 的,一种是基于全局配置的。这两种都由开发者自己用代码来控制客户端路由逻辑,所以我们把这种模式称作【自控式路由】。

执行 pnpm run dev,访问 http://localhost:8080/contacts,可以看到完整的联系人,页面的标题是 All:

display

访问 http://localhost:8080/contacts/archives,只会看到已存档的联系人,页面的标题是 Archives:

display1

查看页面 HTML 源码,可以看到两个页面的内容是一样,是在客户端针对不同 URL 渲染不同内容。

接下来我们增加一个简单的导航栏,让用户能在两个列表之间切换

打开 src/contacts/App.tsx,在顶部导入 Radio 组件:

import { List, Radio } from 'antd';

然后将 UI 最顶部进行修改,增加一组单选框

return (
<div className="container lg mx-auto">
<div className="h-16 p-2 flex items-center justify-center">
<Radio.Group onChange={handleSetList} value={currentList}>
<Radio value="/">All</Radio>
<Radio value="/archives">Archives</Radio>
</Radio.Group>
</div>
<Switch>

然后我们来实现 currentListhandleSetList

引入两个 React Hook:useStateuseHistory,以及 Ant Design 的事件类型定义:

import { useState } from 'react';
import { List, Radio, RadioChangeEvent } from 'antd';
import { Route, Switch, useHistory } from '@modern-js/runtime/router';

最后在 App 组件里增加局部状态和相关逻辑:

function App() {
const history = useHistory();
const [currentList, setList] = useState(history.location.pathname || '/');
const handleSetList = (e: RadioChangeEvent) => {
const { value } = e.target;
setList(value);
history.push(value);
};

到这里就已经完成了页面导航栏实现,执行 pnpm run dev 查看效果:

display2

点击导航栏中 Archives,可以看到单选框的选中状态和 URL 都会变化,页面没有刷新,只发生了 CSR。

如果我们将 contacts 入口的 SSR 选项开启后(配置教程),重新访问两个页面,可以看到 HTML 内容是不同的,这是因为在 SSR 阶段页面就执行了客户端路由的逻辑,HTML 里已经包含了最终的渲染结果。

访问 http://localhost:8080/contacts/archives,点击顶部单选框,可以看到在有 SSR 的情况下,CSR 不受影响,跟开启 SSR 之前的效果一致,实现了 UX 的最大化(首屏 SSR,后续交互 CSR)。


本小节的代码可以在这里查看