UNPKG

rerumaccusamus

Version:

The meta-framework suite designed from scratch for frontend-focused modern web development.

322 lines (243 loc) 9.96 kB
--- 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/`,可以看到结果: ![result](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/result.png) 点击 Nav 里的链接,URL 变为 `http://localhost:8080/landing-page/docs`,内容会变为: ![result1](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/result1.png) 我们同样可以开启/关闭 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/`,可以看到效果如下: ![result2](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/result2.png) 点击评论链接,跳转到 `http://localhost:8080/landing-page/comments/i-am-a-comment-title`,效果如下: ![result3](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/result3.png) 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)。