---
title: 使用自控式路由​​​​
---

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

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

我们分别用两种不同的方式，为 `contacts` 和 `landing-page` 增加客户端路由逻辑。

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

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

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

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

```js
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 Router](https://reactrouter.com/) 的 `Route`、`Switch` 和 [React Helmet](https://github.com/nfl/react-helmet) 的 `Helmet` 组件：

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

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

```js
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>
  );
}
```

:::info 注
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](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/display.png)

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

![display1](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/display1.png)

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

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

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

```tsx
import { List, Radio } from 'antd';
```

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

```tsx {3-8}
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>
```

然后我们来实现 `currentList` 和 `handleSetList`。

引入两个 React Hook：`useState` 和 `useHistory`，以及 Ant Design 的事件类型定义：

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

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

```js {2-8}
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](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/display2.png)

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

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

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

---

> 本小节的代码可以在[这里查看](https://github.com/modern-js-dev/modern-js-examples/tree/main/tutorials/c08/hello-modern)。
