rerumaccusamus
Version:
The meta-framework suite designed from scratch for frontend-focused modern web development.
322 lines (243 loc) • 9.96 kB
Markdown
---
title: 使用约定式路由
---
上一小节中,我们学习了如何使用【 自控式路由 】方式实现客户端路由。
这一小节中,我们将在 landing-page 入口里,学习使用【 约定式路由 】。
我们首先将 `landing-page` 入口的**标识** `App.tsx` 删除,换成另一种符合约定的**标识** `pages/`。
我们在终端执行以下代码修改 `landing-page` 文件内容:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="macOS" label="macOS" default>
```bash
rm src/landing-page/App.tsx src/landing-page/App.css
mkdir -p src/landing-page/pages/
touch src/landing-page/pages/_app.tsx src/landing-page/pages/index.tsx src/landing-page/pages/docs.tsx
```
</TabItem>
<TabItem value="Windows" label="Windows">
```powershell
rm src/landing-page/App.tsx
rm src/landing-page/App.css
mkdir -p src/landing-page/pages/
ni src/landing-page/pages/_app.tsx
ni src/landing-page/pages/index.tsx
ni src/landing-page/pages/docs.tsx
```
</TabItem>
</Tabs>
`pages/` 跟 `App.tsx` 一样,都起到入口**标识**的作用,让 `src/landing-page` 被识别为入口。
如果存在 `App.tsx` 则优先使用 `App.tsx` 作为编译入口。而 `pages/` 则默认启用【 约定式路由 】的客户端路由实现方式,`pages/` 中的文件路径和文件内容,将被自动自动转换成客户端路由逻辑。
执行命令后,项目结构如下:
```md
.
├── .vscode/
│ ├── extensions.json
│ └── settings.json
├── src/
│ ├── contacts/
│ │ ├── components/
│ │ │ ├── Avatar/
│ │ │ │ ├── index.stories.tsx
│ │ │ │ └── index.tsx
│ │ │ └── Item/
│ │ │ ├── index.test.tsx
│ │ │ └── index.tsx
│ │ ├── styles/
│ │ │ └── utils.css
│ │ ├── App.css
│ │ └── App.tsx
│ ├── landing-page/
│ │ └── pages/
│ │ ├── _app.tsx
│ │ ├── docs.tsx
│ │ └── index.tsx
│ ├── .eslintrc.json
│ └── modern-app-env.d.ts
├── .editorconfig
├── .gitignore
├── .npmrc
├── .nvmrc
├── README.md
├── package.json
├── pnpm-lock.yaml
└── tsconfig.json
```
注:以上展示的是手动创建入口的方式。
以下介绍自动启用方式,开发者可在自己的项目中尝试:
在 modern.js 项目中,执行`pnpm run new`命令, 按照以下选项选择,实现创建入口时,自动启用【 约定式路由 】:
```bash
# 请选择你想要的操作:
❯ 新建「应用入口」
#填写入口名称:
landing-page
# 是否需要调整默认配置?
❯ 是
# 请选择客户端路由方式
启用自控路由
❯ 启用约定式路由
不启用
# 是否启用应用状态管理?
❯ 否
```
假设这个 `landing-page` 入口是一个简单的有导航栏的落地页,包含两个子页面:一个首页和一个文档页,我们用客户端路由的方式来区别这两个子页面。
我们先分别实现这个入口两个子页面的内容,`src/landing-page/pages/index.tsx` 是首页,内容为:
```js
import { Helmet } from '@modern-js/runtime/head';
const Index = () => (
<>
<Helmet>
<title>Home</title>
</Helmet>
<p>
Welcome to <a href="/contacts">Contact List</a>!
</p>
</>
);
export default Index;
```
`src/landing-page/pages/docs.tsx` 是文档页,内容为:
```js
import { Helmet } from '@modern-js/runtime/head';
const Docs = () => (
<>
<Helmet>
<title>Docs</title>
</Helmet>
<p>I am docs</p>
</>
);
export default Docs;
```
最后,`src/landing-page/pages/_app.tsx` 是下划线开头的,代表这是【 约定式路由 】中一个特殊的功能文件,为整个入口提供根组件,可以用于实现全局布局、公共 UI 等。
在这里,我们只实现一个简单的导航和结构:
```js
import { ComponentType } from 'react';
import { NavLink } from '@modern-js/runtime/router';
const App = ({ Component, ...pageProps }: { Component: ComponentType }) => (
<div>
<h2>Nav:</h2>
<ul>
<li>
<NavLink
to="/"
exact={true}
activeStyle={{
color: '#f60',
}}>
Home
</NavLink>
</li>
<li>
<NavLink
to="/docs"
activeStyle={{
color: '#f60',
}}>
Docs
</NavLink>
</li>
</ul>
<h2>Content:</h2>
<Component {...pageProps} />
</div>
);
export default App;
```
执行 `pnpm run dev`,访问 `http://localhost:8080/landing-page/`,可以看到结果:

点击 Nav 里的链接,URL 变为 `http://localhost:8080/landing-page/docs`,内容会变为:

我们同样可以开启/关闭 SSR 选项([配置教程](/docs/apis/config/server/ssr)),并查看 HTML 源码。结果和【 自控式路由 】的 contacts 入口一样。
:::note 注
Modern.js Lint 规则集要求 React 组件的文件名或目录名都用 Pascal 命名风格,跟组件本身的名字保持一致,首字母大写。
`pages/` 里面的文件虽然也是 React 组件,但它们是基于约定用于指定路由的特殊组件文件,对应的是 URL 中的路径,所以这里是一种例外情况,文件名/目录名跟 [URL 命名风格](https://geemus.gitbooks.io/http-api-design/content/en/requests/downcase-paths-and-attributes.html)保持一致更好,最好用 dash 命名风格(全小写,- 连字符分隔)。
:::
接下来我们再实现一个简单的评论详情页,因为评论会有很多条,所以评论详情页的 URL 包含动态部分,例如:`http://localhost:8080/landing-page/docs/comments/:commentTitle/`
对于像 `:commentTitle` 这样的动态部分,Modern.js 提供了一种特殊的文件命名方式来实现。
执行以下命令,新建以下文件:
<Tabs>
<TabItem value="macOS" label="macOS" default>
```bash
mkdir -p src/landing-page/pages/comments/\[commentTitle\]
touch src/landing-page/pages/comments/\[commentTitle\]/index.tsx
```
</TabItem>
<TabItem value="Windows" label="Windows">
```powershell
mkdir -p src/landing-page/pages/comments/[commentTitle]
ni src/landing-page/pages/comments/[commentTitle\/index.tsx
```
</TabItem>
</Tabs>
`landing-page` 入口中的文件结构变成:
```md
.
└── pages/
├── comments/
│ └── [commentTitle]/
│ └── index.tsx
├── _app.tsx
├── docs.tsx
└── index.tsx
```
`[commentTitle]` 将会被 Modern.js 转换成动态路由。
我们首先安装文件中需要的依赖:
```bash
pnpm add lodash
pnpm add -D @types/lodash
```
`pages/comments/[commentTitle]/index.tsx` 的内容为:
```js
import { Helmet } from '@modern-js/runtime/head';
import { Link } from '@modern-js/runtime/router';
import _ from 'lodash';
const Index = ({ match: { params } }: any) => {
const title = _.startCase(params.commentTitle);
return (
<>
<Helmet>
<title>Comment: {title}</title>
</Helmet>
<p>
<Link to="/docs/">{'< Back'}</Link>
</p>
<h1>Subject: {title}</h1>
<p>Post: I am a comment</p>
</>
);
};
export default Index;
```
修改 `docs.tsx`,添加跳转到评论页的链接:
```js
import { Helmet } from '@modern-js/runtime/head';
import { Link } from '@modern-js/runtime/router';
const Docs = () => (
<>
<Helmet>
<title>Docs</title>
</Helmet>
<p>I am docs</p>
<p>
<Link to="/comments/i-am-a-comment-title">[Random comment]</Link>
</p>
</>
);
export default Docs;
```
:::note 注
`pages/` 和 `App.tsx` 一样,应该专注于路由的组织,具体的 UI 实现和业务逻辑,一旦复杂到要拆分成多个文件的程度,应该放到 `pages/` 外面,在同一个应用入口目录中,用 **feature 目录**、**role 目录**的方式来组织。
例如使用 `src/landing-page/docs/components/Article/index.tsx` 来组织功能特性。
`landing-page/docs/` 和 `landing-page/pages/docs/` 不同,前者是 **feature 目录**,将修改频率不同或由不同人负责开发的业务逻辑,封装在同一个黑盒里,这种目录下的问题与具体路由解耦。
`pages/` 目录里的组件文件,负责把这些 feature 组件组织到一起,通过特定的路由结构提供给用户。
因此 `pages/` 目录里基本上不应该有组件之外的文件,也不应该有过于复杂和具体的实现。
:::
执行 `pnpm run dev`,访问 `http://localhost:8080/landing-page/docs/`,可以看到效果如下:

点击评论链接,跳转到 `http://localhost:8080/landing-page/comments/i-am-a-comment-title`,效果如下:

URL 中的动态部分,在代码中被转换成了标题内容,修改最后部分 URL 后重新访问,标题内容同样发生改变。
本小节中,我们学习了基本的【 约定式路由 】用法,除了 `_app.tsx` 外,我们还可以使用 [`_layout.tsx`](/docs/apis/hooks/mwa/src/pages#部分-layout) 实现这个访问路径下的公共 UI,可以用 [`404.tsx`](/docs/apis/hooks/mwa/src/pages#404-路由) 自定义 404 页面等。
---
> 本小节的代码可以在[这里查看](https://github.com/modern-js-dev/modern-js-examples/tree/main/tutorials/c08/hello-modern-2)。