@modern-js/main-doc 0.0.0-nightly-20240827170702 → 0.0.0-nightly-20240829170706
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/en/apis/app/runtime/core/use-loader.mdx +1 -1
- package/docs/en/components/ssr-monitor.mdx +3 -0
- package/docs/en/configure/app/output/ssg.mdx +52 -141
- package/docs/en/guides/advanced-features/_meta.json +0 -7
- package/docs/en/guides/basic-features/_meta.json +7 -1
- package/docs/en/guides/basic-features/data/data-fetch.mdx +134 -235
- package/docs/en/guides/basic-features/data/data-write.mdx +66 -77
- package/docs/en/guides/basic-features/render/_meta.json +1 -0
- package/docs/en/guides/basic-features/render/ssg.mdx +208 -0
- package/docs/en/guides/{advanced-features/ssr/cache.mdx → basic-features/render/ssr-cache.mdx} +38 -50
- package/docs/en/guides/basic-features/render/ssr.mdx +301 -0
- package/docs/en/guides/basic-features/render/streaming-ssr.mdx +230 -0
- package/docs/en/guides/basic-features/routes.mdx +275 -263
- package/docs/en/guides/concept/entries.mdx +9 -2
- package/docs/zh/apis/app/runtime/core/use-loader.mdx +1 -1
- package/docs/zh/components/ssr-monitor.mdx +3 -0
- package/docs/zh/configure/app/output/ssg.mdx +49 -139
- package/docs/zh/guides/advanced-features/_meta.json +0 -7
- package/docs/zh/guides/basic-features/_meta.json +7 -1
- package/docs/zh/guides/basic-features/data/data-fetch.mdx +98 -213
- package/docs/zh/guides/basic-features/data/data-write.mdx +54 -55
- package/docs/zh/guides/basic-features/render/_meta.json +1 -0
- package/docs/zh/guides/basic-features/render/ssg.mdx +210 -0
- package/docs/zh/guides/{advanced-features/ssr/cache.mdx → basic-features/render/ssr-cache.mdx} +16 -26
- package/docs/zh/guides/basic-features/render/ssr.mdx +309 -0
- package/docs/zh/guides/{advanced-features/ssr/stream.mdx → basic-features/render/streaming-ssr.mdx} +22 -37
- package/docs/zh/guides/basic-features/routes.mdx +252 -237
- package/docs/zh/guides/concept/entries.mdx +6 -3
- package/package.json +6 -6
- package/docs/en/guides/advanced-features/ssg.mdx +0 -116
- package/docs/en/guides/advanced-features/ssr/_meta.json +0 -1
- package/docs/en/guides/advanced-features/ssr/index.mdx +0 -23
- package/docs/en/guides/advanced-features/ssr/stream.mdx +0 -248
- package/docs/en/guides/advanced-features/ssr/usage.mdx +0 -341
- package/docs/en/guides/advanced-features/ssr.mdx +0 -555
- package/docs/zh/guides/advanced-features/ssg.mdx +0 -116
- package/docs/zh/guides/advanced-features/ssr/_meta.json +0 -1
- package/docs/zh/guides/advanced-features/ssr/index.mdx +0 -23
- package/docs/zh/guides/advanced-features/ssr/usage.mdx +0 -329
@@ -2,20 +2,22 @@
|
|
2
2
|
sidebar_position: 2
|
3
3
|
---
|
4
4
|
|
5
|
-
#
|
5
|
+
# 路由
|
6
6
|
|
7
|
-
Modern.js 的路由基于 [React Router 6](https://reactrouter.com/en/main)
|
7
|
+
Modern.js 的路由基于 [React Router 6](https://reactrouter.com/en/main),提供了基于文件约定的路由能力,并支持了业界流行的约定式路由模式:**嵌套路由**。当入口被识别为 [约定式路由](/guides/concept/entries.html#约定式路由) 时,Modern.js 会自动基于文件系统,生成对应的路由结构。
|
8
8
|
|
9
9
|
:::note
|
10
|
-
|
11
|
-
|
10
|
+
本小节提到的路由,都是约定式路由。
|
12
11
|
:::
|
13
12
|
|
14
|
-
##
|
13
|
+
## 什么是嵌套路由
|
14
|
+
|
15
|
+
嵌套路由是一种将 URL 分段与组件层次结构和数据耦合起来的路由模式。通常,URL 段会决定:
|
15
16
|
|
16
|
-
|
17
|
+
- 页面上要渲染的布局
|
18
|
+
- 这些布局的数据依赖
|
17
19
|
|
18
|
-
|
20
|
+
因此,使用嵌套路由时,页面的路由与 UI 结构是相呼应的,我们将会详细介绍这种路由模式。
|
19
21
|
|
20
22
|
```bash
|
21
23
|
/user/johnny/profile /user/johnny/posts
|
@@ -28,61 +30,44 @@ Modern.js 支持了业界流行的约定式路由模式:**嵌套路由**,使
|
|
28
30
|
+------------------+ +-----------------+
|
29
31
|
```
|
30
32
|
|
31
|
-
|
33
|
+
## 路由文件约定
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
例如以下目录结构:
|
35
|
+
在 `routes/` 目录下,子目录名会作为路由 URL 的映射,Modern.js 有两个文件约定 `layout.tsx` 和 `page.tsx`。这两个文件决定了应用的布局层次,其中:
|
36
36
|
|
37
|
-
|
38
|
-
.
|
39
|
-
└── routes
|
40
|
-
├── page.tsx
|
41
|
-
└── user
|
42
|
-
└── page.tsx
|
43
|
-
```
|
37
|
+
- `page.tsx` 为内容组件,所在目录下存在该文件时,对应的路由 URL 可访问。
|
38
|
+
- `layout.tsx` 为布局组件,控制所在目录下所有子路由的布局,使用 `<Outlet>` 表示子组件。
|
44
39
|
|
45
|
-
|
40
|
+
:::tip
|
41
|
+
`.ts`、`.js`、`.jsx` 或 `.tsx` 文件扩展名可用于上述约定文件。
|
42
|
+
:::
|
46
43
|
|
47
|
-
|
48
|
-
- `/user`
|
44
|
+
### Page
|
49
45
|
|
50
|
-
|
46
|
+
`<Page>` 组件是指 `routes/` 目录下所有 `page.tsx` 文件,也是所有路由的叶子组件。除了通配路由外,任何路由都应该由 `<Page>` 组件结束。
|
51
47
|
|
52
|
-
|
53
|
-
|
48
|
+
```tsx title=routes/page.tsx
|
49
|
+
export default () => {
|
50
|
+
return <div>Hello world</div>
|
51
|
+
};
|
52
|
+
```
|
54
53
|
|
55
|
-
|
54
|
+
当应用中存在以下目录结构时:
|
56
55
|
|
57
56
|
```bash
|
58
57
|
.
|
59
58
|
└── routes
|
60
|
-
├── layout.tsx
|
61
59
|
├── page.tsx
|
62
60
|
└── user
|
63
|
-
├── layout.tsx
|
64
61
|
└── page.tsx
|
65
62
|
```
|
66
63
|
|
67
|
-
|
68
|
-
|
69
|
-
```tsx
|
70
|
-
<Layout>
|
71
|
-
<Page />
|
72
|
-
</Layout>
|
73
|
-
```
|
64
|
+
会产出下面两条路由:
|
74
65
|
|
75
|
-
|
66
|
+
- `/`
|
67
|
+
- `/user`
|
76
68
|
|
77
|
-
```tsx
|
78
|
-
<Layout>
|
79
|
-
<UserLayout>
|
80
|
-
<UserPage />
|
81
|
-
</UserLayout>
|
82
|
-
</Layout>
|
83
|
-
```
|
84
69
|
|
85
|
-
|
70
|
+
### Layout
|
86
71
|
|
87
72
|
`<Layout>` 组件是指 `routes/` 目录下所有 `layout.tsx` 文件,它们表示对应路由片段的布局,使用 `<Outlet>` 表示子组件。
|
88
73
|
|
@@ -99,11 +84,10 @@ export default () => {
|
|
99
84
|
```
|
100
85
|
|
101
86
|
:::note
|
102
|
-
`<Outlet>` 是 React Router 6
|
103
|
-
|
87
|
+
`<Outlet>` 是 React Router 6 中提供的 API,详情可以查看 [Outlet](https://reactrouter.com/en/main/components/outlet#outlet)。
|
104
88
|
:::
|
105
89
|
|
106
|
-
|
90
|
+
不同目录结构下,`<Outlet>` 所代表的组件也不同。为了方便介绍 `<Layout>` 与 `<Outlet>` 的关系,以下面的文件目录举例:
|
107
91
|
|
108
92
|
```bash
|
109
93
|
.
|
@@ -117,7 +101,7 @@ export default () => {
|
|
117
101
|
└── page.tsx
|
118
102
|
```
|
119
103
|
|
120
|
-
1. 当路由为 `/` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/page.tsx`
|
104
|
+
1. 当路由为 `/` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/page.tsx` 中导出的组件,路由的 UI 结构为:
|
121
105
|
|
122
106
|
```tsx
|
123
107
|
<Layout>
|
@@ -125,7 +109,7 @@ export default () => {
|
|
125
109
|
</Layout>
|
126
110
|
```
|
127
111
|
|
128
|
-
2. 当路由为 `/blog` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/blog/page.tsx`
|
112
|
+
2. 当路由为 `/blog` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/blog/page.tsx` 中导出的组件,路由的 UI 结构为:
|
129
113
|
|
130
114
|
```tsx
|
131
115
|
<Layout>
|
@@ -133,7 +117,7 @@ export default () => {
|
|
133
117
|
</Layout>
|
134
118
|
```
|
135
119
|
|
136
|
-
3. 当路由为 `/user` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/user/layout.tsx` 中导出的组件。`routes/user/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/user/page.tsx`
|
120
|
+
3. 当路由为 `/user` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/user/layout.tsx` 中导出的组件。`routes/user/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/user/page.tsx` 中导出的组件,路由的 UI 结构为:
|
137
121
|
|
138
122
|
```tsx
|
139
123
|
<Layout>
|
@@ -145,43 +129,16 @@ export default () => {
|
|
145
129
|
|
146
130
|
总结而言,如果子路由的文件目录下存在 `layout.tsx`,上一级 `layout.tsx` 中的 `<Outlet>` 即为子路由文件目录下的 `layout.tsx` ,否则为子路由文件目录下的 `page.tsx`。
|
147
131
|
|
148
|
-
|
149
|
-
|
150
|
-
所有的路由,理论上都应该由 `<Page>` 组件结束。在 `page.tsx` 文件内,如果开发者引入 `<Outlet>` 组件,不会有任何效果。
|
151
|
-
|
152
|
-
#### Config
|
153
|
-
|
154
|
-
每个 `Layout`, `$` 或 `Page` 文件都可以定义一个自己的 `config` 文件,如 `page.config.ts`,该文件中我们约定了一个具名导出 `handle`,
|
155
|
-
这个字段中你可以定义任意属性:
|
156
|
-
|
157
|
-
```ts title="routes/page.config.ts"
|
158
|
-
export const handle = {
|
159
|
-
breadcrumbName: 'profile',
|
160
|
-
};
|
161
|
-
```
|
162
|
-
|
163
|
-
定义的这些属性可以通过 [`useMatches`](https://reactrouter.com/en/main/hooks/use-matches) hook 获取:
|
164
|
-
|
165
|
-
```ts title="routes/layout.ts"
|
166
|
-
export default () => {
|
167
|
-
const matches = useMatches();
|
168
|
-
const breadcrumbs = matches.map(
|
169
|
-
matchedRoute => matchedRoute?.handle?.breadcrumbName,
|
170
|
-
);
|
171
|
-
return <Breadcrumb names={breadcrumbs}></Breadcrumb>;
|
172
|
-
};
|
173
|
-
```
|
174
|
-
|
175
|
-
### 动态路由
|
132
|
+
## 动态路由
|
176
133
|
|
177
134
|
通过 `[]` 命名的文件目录,生成的路由会作为动态路由。例如以下文件目录:
|
178
135
|
|
179
136
|
```bash
|
137
|
+
.
|
180
138
|
└── routes
|
181
|
-
├── [id]
|
182
|
-
│ └── page.tsx
|
183
139
|
├── blog
|
184
|
-
│
|
140
|
+
│ └── [id]
|
141
|
+
│ └── page.tsx
|
185
142
|
└── page.tsx
|
186
143
|
```
|
187
144
|
|
@@ -189,48 +146,71 @@ export default () => {
|
|
189
146
|
|
190
147
|
在组件中,可以通过 [useParams](/apis/app/runtime/router/router#useparams) 获取对应命名的参数。
|
191
148
|
|
192
|
-
|
149
|
+
```tsx
|
150
|
+
import { useParams } from '@modern-js/runtime/router';
|
193
151
|
|
194
|
-
|
152
|
+
function Blog() {
|
153
|
+
const { id } = useParams();
|
154
|
+
return <div>current blog ID is: {id}</div>;
|
155
|
+
}
|
156
|
+
export default Blog;
|
157
|
+
```
|
158
|
+
|
159
|
+
## 动态可选路由
|
195
160
|
|
196
161
|
通过 `[$]` 命名的文件目录,生成的路由会作为动态可选路由。例如以下文件目录:
|
197
162
|
|
198
163
|
```bash
|
164
|
+
.
|
199
165
|
└── routes
|
200
|
-
├──
|
166
|
+
├── blog
|
201
167
|
│ └── [id$]
|
202
168
|
│ └── page.tsx
|
203
|
-
├── blog
|
204
|
-
│ └── page.tsx
|
205
169
|
└── page.tsx
|
206
170
|
```
|
207
171
|
|
208
172
|
`routes/user/[id$]/page.tsx` 文件会转为 `/user/:id?` 路由。`/user` 下的所有路由都会匹配到该路由,并且 `id` 参数可选存在。通常在区分**创建**与**编辑**时,可以使用该路由。
|
209
173
|
|
210
|
-
|
174
|
+
```tsx
|
175
|
+
import { useParams } from '@modern-js/runtime/router';
|
176
|
+
|
177
|
+
function Blog() {
|
178
|
+
const { id } = useParams();
|
179
|
+
if (id) {
|
180
|
+
return <div>current blog ID is: {id}</div>;
|
181
|
+
}
|
182
|
+
|
183
|
+
return <div>create new blog</div>;
|
184
|
+
}
|
185
|
+
export default Blog;
|
186
|
+
```
|
211
187
|
|
212
|
-
在 loader 中,params 会作为 [loader](/guides/basic-features/data/data-fetch#loader-函数) 的入参,通过 `params.xxx` 可以获取。
|
213
188
|
|
214
|
-
|
189
|
+
## 通配路由
|
215
190
|
|
216
|
-
|
191
|
+
如果在某个子目录下存在 `$.tsx` 文件,该文件会作为通配路由组件,当没有匹配的路由时,会渲染该路由组件。
|
217
192
|
|
218
193
|
:::note
|
219
|
-
`$.tsx` 可以认为是一种特殊的
|
194
|
+
`$.tsx` 可以认为是一种特殊的 `<Page>` 组件,如果路由无法匹配,则 `$.tsx` 会作为 `<Layout>` 的子组件渲染。
|
195
|
+
:::
|
196
|
+
|
197
|
+
:::warning
|
198
|
+
如果当前目录下不存在 `<Layout>` 组件时,则 `$.tsx` 不会生效。
|
220
199
|
:::
|
221
200
|
|
222
201
|
例如以下目录结构:
|
223
202
|
|
224
203
|
```bash
|
204
|
+
.
|
225
205
|
└── routes
|
226
206
|
├── blog
|
227
207
|
│ ├── $.tsx
|
228
208
|
│ └── layout.tsx
|
229
|
-
|
209
|
+
├── layout.tsx
|
230
210
|
└── page.tsx
|
231
211
|
```
|
232
212
|
|
233
|
-
|
213
|
+
当你访问 `/blog/a` 时,无法匹配到任意路由,则页面会渲染 `routes/blog/$.tsx` 组件,路由的 UI 结构为:
|
234
214
|
|
235
215
|
```tsx
|
236
216
|
<RootLayout>
|
@@ -246,16 +226,70 @@ export default () => {
|
|
246
226
|
|
247
227
|
```ts title="$.tsx"
|
248
228
|
import { useParams } from '@modern-js/runtime/router';
|
249
|
-
|
250
|
-
|
251
|
-
|
229
|
+
|
230
|
+
function Blog() {
|
231
|
+
// 当 path 是 `/blog/aaa/bbb` 时
|
232
|
+
const params = useParams();
|
233
|
+
console.log(params) // ---> { '*': 'aaa/bbb' }
|
234
|
+
|
235
|
+
return <div>current blog URL is {params["*"]}</div>;
|
236
|
+
}
|
237
|
+
export default Blog;
|
252
238
|
```
|
253
239
|
|
254
|
-
|
240
|
+
### 定制 404 页面
|
255
241
|
|
256
|
-
|
242
|
+
通配路由可以被添加到 `routes/` 目录下的任意子目录中,一种常见的使用场景是通过 `$.tsx` 文件去定制任意层级的 404 内容。
|
257
243
|
|
258
|
-
|
244
|
+
例如你需要对所有未匹配到的路由,都展示一个 404 页面,可以添加 `routes/$.tsx` 文件:
|
245
|
+
|
246
|
+
```bash
|
247
|
+
.
|
248
|
+
└── routes
|
249
|
+
├── $.tsx
|
250
|
+
├── blog
|
251
|
+
│ └── [id$]
|
252
|
+
│ └── page.tsx
|
253
|
+
├── layout.tsx
|
254
|
+
└── page.tsx
|
255
|
+
```
|
256
|
+
|
257
|
+
```tsx
|
258
|
+
function Page404() {
|
259
|
+
return <div>404 Not Found</div>;
|
260
|
+
}
|
261
|
+
export default Page404;
|
262
|
+
```
|
263
|
+
|
264
|
+
此时,当访问除了 `/` 或 `/blog/*` 以外的路由时,都会匹配到 `routes/$.tsx` 组件,展示 404 页面。
|
265
|
+
|
266
|
+
## 路由句柄配置
|
267
|
+
|
268
|
+
某些场景下,每个路由会拥有属于自己的数据,应用需要在其他组件中获取匹配到的路由的这些数据。一个常见的例子是在不居中获取到匹配路由的面包屑信息。
|
269
|
+
|
270
|
+
Modern.js 提供了独立的约定,每个 `Layout`, `$` 或 `Page` 文件都可以定义一个自己的 `config` 文件,如 `page.config.ts`,该文件中我们约定了一个具名导出 `handle`,这个字段中你可以定义任意属性:
|
271
|
+
|
272
|
+
```ts title="routes/page.config.ts"
|
273
|
+
export const handle = {
|
274
|
+
breadcrumbName: 'profile',
|
275
|
+
};
|
276
|
+
```
|
277
|
+
|
278
|
+
定义的这些属性可以通过 [`useMatches`](https://reactrouter.com/en/main/hooks/use-matches) hook 获取。
|
279
|
+
|
280
|
+
```ts title="routes/layout.ts"
|
281
|
+
export default () => {
|
282
|
+
const matches = useMatches();
|
283
|
+
const breadcrumbs = matches.map(
|
284
|
+
matchedRoute => matchedRoute?.handle?.breadcrumbName,
|
285
|
+
);
|
286
|
+
return <Breadcrumb names={breadcrumbs}></Breadcrumb>;
|
287
|
+
};
|
288
|
+
```
|
289
|
+
|
290
|
+
## 无路径布局
|
291
|
+
|
292
|
+
当目录名以 `__` 开头时,对应的目录名不会转换为实际的路由路径,例如以下文件目录:
|
259
293
|
|
260
294
|
```bash
|
261
295
|
.
|
@@ -264,19 +298,19 @@ params['*']; // => 'aaa/bbb'
|
|
264
298
|
│ ├── layout.tsx
|
265
299
|
│ ├── login
|
266
300
|
│ │ └── page.tsx
|
267
|
-
│ └──
|
301
|
+
│ └── sign
|
268
302
|
│ └── page.tsx
|
269
303
|
├── layout.tsx
|
270
304
|
└── page.tsx
|
271
305
|
```
|
272
306
|
|
273
|
-
Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组件会作为 `login/page.tsx` 和 `
|
307
|
+
Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组件会作为 `login/page.tsx` 和 `sign/page.tsx` 的布局组件,但`__auth` 不会作为路由路径片段出现在用户访问的 URL 中。
|
274
308
|
|
275
309
|
当需要为某些类型的路由,做独立的布局,或是想要将路由做归类时,这一功能非常有用。
|
276
310
|
|
277
|
-
|
311
|
+
## 无布局路径
|
278
312
|
|
279
|
-
有些情况下,项目需要较为复杂的路由,但这些路由又不存在独立的 UI
|
313
|
+
有些情况下,项目需要较为复杂的路由,但这些路由又不存在独立的 UI 布局,如果像普通文件目录那样创建路由会导致目录层级较深。
|
280
314
|
|
281
315
|
因此 Modern.js 支持了通过 `.` 来分割路由片段,代替文件目录。例如,当需要 `/user/profile/2022/edit` 时,可以直接创建如下文件:
|
282
316
|
|
@@ -292,15 +326,93 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
|
|
292
326
|
|
293
327
|
```tsx
|
294
328
|
<RootLayout>
|
295
|
-
|
329
|
+
{/* routes/user.profile.[id].edit/page.tsx */}
|
330
|
+
<UserProfileEdit />
|
296
331
|
</RootLayout>
|
297
332
|
```
|
298
333
|
|
299
|
-
|
334
|
+
## 路由重定向
|
335
|
+
|
336
|
+
某些应用中,可能需要根据用户的身份信息,或是其他数据条件,选择重定向到其他路由。在 Modern.js 中,你可以使用 [`Data Loader`](/guides/basic-features/data/data-fetch) 文件来获取数据,或是和传统 React 组件那样,在 `useEffect` 中请求数据。
|
337
|
+
|
338
|
+
### 在 Data Loader 中重定向
|
339
|
+
|
340
|
+
在任意的 `page.tsx` 同级目录中创建,`page.data.ts` 文件,这个文件就是该路由的 Data Loader。在 Data Loader 中,你可以通过调用 `redirect` API 来完成路由的重定向。
|
341
|
+
|
342
|
+
```ts title="routes/user/page.data.ts"
|
343
|
+
import { redirect } from '@modern-js/runtime/router';
|
344
|
+
|
345
|
+
export const loader = () => {
|
346
|
+
const user = await getUser();
|
347
|
+
if (!user) {
|
348
|
+
return redirect('/login');
|
349
|
+
}
|
350
|
+
return null;
|
351
|
+
};
|
352
|
+
```
|
353
|
+
|
354
|
+
### 在组件中重定向
|
355
|
+
|
356
|
+
在组件内做重定向,则可以通过 `useNavigate` hook,示例如下:
|
357
|
+
|
358
|
+
```ts title="routes/user/page.ts"
|
359
|
+
import { useNavigate } from '@modern-js/runtime/router';
|
360
|
+
import { useEffect } from 'react';
|
361
|
+
|
362
|
+
export default () => {
|
363
|
+
const navigate = useNavigate();
|
364
|
+
useEffect(() => {
|
365
|
+
getUser().then(user => {
|
366
|
+
if (!user) {
|
367
|
+
navigate('/login');
|
368
|
+
}
|
369
|
+
});
|
370
|
+
});
|
371
|
+
|
372
|
+
return <div>Hello World</div>;
|
373
|
+
};
|
374
|
+
```
|
375
|
+
|
376
|
+
## 错误处理
|
377
|
+
|
378
|
+
`routes/` 下每一层目录中,开发者同样可以定义一个 `error.tsx` 文件,默认导出一个 `<ErrorBoundary>` 组件。当路由目录下存在该组件时,组件渲染出错会被 `ErrorBoundary` 组件捕获。
|
379
|
+
|
380
|
+
`<ErrorBoundary>` 可以返回出错时的 UI 视图,当前层级未声明 `<ErrorBoundary>` 组件时,错误会向上冒泡到更上层的组件,直到被捕获或抛出错误。同时,当组件出错时,只会影响捕获到该错误的路由组件及子组件,其他组件的状态和视图不受影响,可以继续交互。
|
381
|
+
|
382
|
+
{/* Todo API 路由 */}
|
383
|
+
|
384
|
+
在 `<ErrorBoundary>` 组件内,可以使用 [useRouteError](/apis/app/runtime/router/router#userouteerror) 获取的错误的具体信息:
|
385
|
+
|
386
|
+
```tsx
|
387
|
+
import { useRouteError } from '@modern-js/runtime/router';
|
388
|
+
|
389
|
+
const ErrorBoundary = () => {
|
390
|
+
const error = useRouteError();
|
391
|
+
return (
|
392
|
+
<div>
|
393
|
+
<h1>{error.status}</h1>
|
394
|
+
<h2>{error.message}</h2>
|
395
|
+
</div>
|
396
|
+
);
|
397
|
+
};
|
398
|
+
export default ErrorBoundary;
|
399
|
+
```
|
400
|
+
|
401
|
+
## Loading (Experimental)
|
300
402
|
|
301
|
-
|
403
|
+
:::info Experimental
|
404
|
+
此功能当前是实验性功能,后续 API 可能有调整。
|
405
|
+
:::
|
406
|
+
|
407
|
+
在约定式路由下, Modern.js 会根据路由,自动地对路由进行分片(每一个路由都作为单独的 JS Chunk 进行加载),当用户访问具体的路由时,再自动加载对应的分片。这样可以有效地减少首屏加载的时间。但这也带来了一个问题,当用户访问一个路由时,如果该路由对应的分片还未加载完成,就会出现白屏的情况。
|
408
|
+
|
409
|
+
Modern.js 支持通过 `loading.tsx` 文件来解决这个问题,`routes/` 下每一层目录中,都可以创建 `loading.tsx` 文件,默认导出一个 `<Loading>` 组件。
|
410
|
+
|
411
|
+
:::warning
|
412
|
+
如果当前目录下不存在 `<Layout>` 组件时,则 `loading.tsx` 不会生效。为了保证用户体验,Modern.js 推荐每个应用添加根 Loading 组件。
|
413
|
+
:::
|
302
414
|
|
303
|
-
|
415
|
+
当路由目录下同时存在该组件和 `layout` 组件时,这一级路由下所有的子路由切换时,会先展示 `loading.tsx` 中导出的组件 UI,直到对应的 JS Chunk 加载完成。例如以下文件目录:
|
304
416
|
|
305
417
|
```bash
|
306
418
|
.
|
@@ -314,9 +426,9 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
|
|
314
426
|
└── page.tsx
|
315
427
|
```
|
316
428
|
|
317
|
-
当定义 `loading.tsx`
|
429
|
+
当定义 `loading.tsx` 时,当路由从 `/` 跳转到 `/blog`,或从 `/blog` 跳转到 `/blog/123` 时,如果路由对应的 JS Chunk 还未加载,都会先展示 `loading.tsx` 中导出的组件 UI。相当于最终的 UI 结构如下:
|
318
430
|
|
319
|
-
```tsx title=当路由为"/"时
|
431
|
+
```tsx title=当路由为 "/" 时
|
320
432
|
<Layout>
|
321
433
|
<Suspense fallback={<Loading />}>
|
322
434
|
<Page />
|
@@ -324,7 +436,7 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
|
|
324
436
|
</Layout>
|
325
437
|
```
|
326
438
|
|
327
|
-
```tsx title=当路由为"/blog"时
|
439
|
+
```tsx title=当路由为 "/blog" 时
|
328
440
|
<Layout>
|
329
441
|
<Suspense fallback={<Loading />}>
|
330
442
|
<BlogPage />
|
@@ -332,7 +444,7 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
|
|
332
444
|
</Layout>
|
333
445
|
```
|
334
446
|
|
335
|
-
```tsx title=当路由为"/blog/123"时
|
447
|
+
```tsx title=当路由为 "/blog/123" 时
|
336
448
|
<Layout>
|
337
449
|
<Suspense fallback={<Loading />}>
|
338
450
|
<BlogIdPage />
|
@@ -340,72 +452,42 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
|
|
340
452
|
</Layout>
|
341
453
|
```
|
342
454
|
|
343
|
-
|
344
|
-
当目录的 Layout 组件不存在时,该目录下的 Loading 组件也不会生效。
|
345
|
-
Modern.js 建议必须有根 Layout 和根 Loading。
|
346
|
-
|
347
|
-
:::
|
348
|
-
|
349
|
-
当路由从 `/` 跳转到 `/blog` 时,如果 `blog/page` 组件的 JS Chunk 还未加载,则会先展示 `loading.tsx` 中导出的组件 UI。
|
455
|
+
## 预加载
|
350
456
|
|
351
|
-
|
457
|
+
大多数切换路由时白屏的情况,都可以通过定义 `<Loading>` 组件来优化体验。Modern.js 也支持在 `<Link>` 组件上定义 `prefetch` 属性,提前对静态资源和数据进行加载。
|
352
458
|
|
353
|
-
|
459
|
+
对于有更高性能要求的应用,预加载可以进一步提升用户体验,减少展示 `<Loading>` 组件的时间:
|
354
460
|
|
355
|
-
|
356
|
-
|
357
|
-
```ts title="routes/user/page.data.ts"
|
358
|
-
import { redirect } from '@modern-js/runtime/router';
|
359
|
-
|
360
|
-
export const loader = () => {
|
361
|
-
const user = await getUser();
|
362
|
-
if (!user) {
|
363
|
-
return redirect('/login');
|
364
|
-
}
|
365
|
-
return null;
|
366
|
-
};
|
461
|
+
```tsx
|
462
|
+
<Link prefetch="intent" to="page">
|
367
463
|
```
|
368
464
|
|
369
|
-
|
465
|
+
:::tip
|
370
466
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
export default () => {
|
375
|
-
const navigate = useNavigate();
|
376
|
-
navigate('/login');
|
377
|
-
};
|
378
|
-
```
|
467
|
+
- 该功能目前仅在 Webpack 项目中支持,Rspack 项目暂不支持。
|
468
|
+
- 对数据的预加载目前只会预加载 SSR 项目中 [Data Loader](/guides/basic-features/data/data-fetch) 中返回的数据。
|
379
469
|
|
380
|
-
|
470
|
+
:::
|
381
471
|
|
382
|
-
`
|
472
|
+
`prefetch` 属性有三个可选值:
|
383
473
|
|
384
|
-
|
474
|
+
- `none`, 默认值,不会做 prefetch,没有任何额外的行为。
|
475
|
+
- `intent`,这是我们推荐大多数场景下使用的值,当你把鼠标放在 Link 上时,会自动开始加载对应的分片和 Data Loader 中定义的数据,当鼠标移开时,会自动取消加载。在我们的测试中,即使是快速点击,也能减少大约 200ms 的加载时间。
|
476
|
+
- `render`,当 `<Link>` 组件渲染时,就会加载对应的分片和 Data Loader 中定义的数据。
|
385
477
|
|
386
|
-
|
478
|
+
:::details 值为 render 和不做路由分片的区别
|
479
|
+
- `render` 加载路由分片的时机是可控的,只有在 `<Link>` 组件进入视窗时才触发,可以通过控制 `<Link>` 组件的渲染位置来控制分片加载时机。
|
480
|
+
- `render` 仅在空闲时对静态资源进行加载,不占用重要模块的加载时间。
|
481
|
+
- 除了预加载路由分片,`render` 在 SSR 项目中还会发起数据预取。
|
387
482
|
|
388
|
-
|
483
|
+
:::
|
389
484
|
|
390
|
-
在 `<ErrorBoundary>` 组件内,可以使用 [useRouteError](/apis/app/runtime/router/router#userouteerror) 获取的错误的具体信息:
|
391
485
|
|
392
|
-
|
393
|
-
import { useRouteError } from '@modern-js/runtime/router';
|
394
|
-
const ErrorBoundary = () => {
|
395
|
-
const error = useRouteError();
|
396
|
-
return (
|
397
|
-
<div>
|
398
|
-
<h1>{error.status}</h1>
|
399
|
-
<h2>{error.message}</h2>
|
400
|
-
</div>
|
401
|
-
);
|
402
|
-
};
|
403
|
-
export default ErrorBoundary;
|
404
|
-
```
|
486
|
+
## 运行时配置
|
405
487
|
|
406
|
-
|
488
|
+
{/* Todo 移到动运行时配置章节 */}
|
407
489
|
|
408
|
-
|
490
|
+
在根 `<Layout>` 组件中(`routes/layout.ts`),可以动态地定义运行时配置:
|
409
491
|
|
410
492
|
```tsx title="src/routes/layout.tsx"
|
411
493
|
// 定义运行时配置
|
@@ -427,7 +509,9 @@ export const config = (): AppConfig => {
|
|
427
509
|
};
|
428
510
|
```
|
429
511
|
|
430
|
-
|
512
|
+
## 渲染前的钩子
|
513
|
+
|
514
|
+
{/* Todo 移到动运行时配置章节 */}
|
431
515
|
|
432
516
|
在有些场景下,需要在应用渲染前做一些操作,可以在 `routes/layout.tsx` 中定义 `init` 钩子,`init` 在客户端和服务端均会执行,基本使用示例如下:
|
433
517
|
|
@@ -490,91 +574,22 @@ export const init = (context: RuntimeContext) => {
|
|
490
574
|
};
|
491
575
|
```
|
492
576
|
|
493
|
-
### 预加载
|
494
|
-
|
495
|
-
在约定式路由下, Modern.js 会根据路由,自动地对路由进行分片。当用户访问具体的路由时,会自动加载对应的分片,这样可以有效地减少首屏加载的时间。但这也带来了一个问题,当用户访问一个路由时,如果该路由对应的分片还未加载完成,就会出现白屏的情况。
|
496
|
-
这种情况下你可以通过定义 `Loading` 组件,在静态资源加载完成前,展示一个自定义的 `Loading` 组件。
|
497
|
-
|
498
|
-
为了进一步提升用户体验,减少 loading 的时间,Modern.js 支持在 Link 组件上定义 `prefetch` 属性,可以提前对静态资源和数据进行加载:
|
499
|
-
|
500
|
-
```tsx
|
501
|
-
<Link prefetch="intent" to="page">
|
502
|
-
```
|
503
|
-
|
504
|
-
:::info
|
505
|
-
|
506
|
-
- 该功能目前仅在 Webpack 项目中支持,Rspack 项目暂不支持。
|
507
|
-
- 对数据的预加载目前只会预加载 SSR 项目中 [Data Loader](/guides/basic-features/data/data-fetch) 中返回的数据。
|
508
|
-
|
509
|
-
:::
|
510
|
-
|
511
|
-
`prefetch` 属性有三个可选值:
|
512
|
-
|
513
|
-
- `none`, 默认值,不会做 prefetch,没有任何额外的行为。
|
514
|
-
- `intent`,这是我们推荐大多数场景下使用的值,当你把鼠标放在 Link 上时,会自动开始加载对应的分片和 Data Loader 中定义的数据,当鼠标移开时,会自动取消加载。在我们的测试中,即使是快速点击,也能减少大约 200ms 的加载时间。
|
515
|
-
- `render`,当 Link 组件渲染时,就会加载对应的分片和 Data Loader 中定义的数据。
|
516
|
-
|
517
|
-
#### 常见问题
|
518
|
-
|
519
|
-
1. 使用 `render` 和不根据路由做静态资源分片的区别?
|
520
|
-
|
521
|
-
- 使用 `render` 可以指定哪些路由在首屏时,进行加载,同时你可以通过对渲染的控制,仅当 `Link` 组件进入到可视区域时,才对 `Link` 组件进行渲染。
|
522
|
-
- 使用 `render`,仅在空闲时对静态资源进行加载,不会与首屏静态资源抢占网络。
|
523
|
-
- 在 SSR 场景下,也会对数据进行预取。
|
524
|
-
|
525
577
|
import Motivation from '@site-docs/components/convention-routing-motivation';
|
526
578
|
|
527
579
|
<Motivation />
|
528
580
|
|
529
|
-
|
581
|
+
## 常见问题
|
530
582
|
|
531
|
-
|
583
|
+
1. 为什么要提供 `@modern-js/runtime/router` 来导出 React Router API ?
|
532
584
|
|
533
|
-
|
585
|
+
可以发现,在文档中所有的代码用例都是使用 `@modern-js/runtime/router` 包导出的 API,而不是直接使用 React Router 包导出的 API。那两者有什么区别呢?
|
534
586
|
|
535
|
-
|
587
|
+
首先,在 `@modern-js/runtime/router` 中导出的 API 和 React Router 包的 API 是完全一致的,如果某个 API 使用出现问题,请先检查 React Router 的文档和 Issues。
|
536
588
|
|
537
|
-
|
538
|
-
import { BrowserRouter, Route, Routes } from '@modern-js/runtime/router';
|
539
|
-
|
540
|
-
export default () => {
|
541
|
-
return (
|
542
|
-
<BrowserRouter>
|
543
|
-
<Routes>
|
544
|
-
<Route index element={<div>index</div>} />
|
545
|
-
<Route path="about" element={<div>about</div>} />
|
546
|
-
</Routes>
|
547
|
-
</BrowserRouter>
|
548
|
-
);
|
549
|
-
};
|
550
|
-
```
|
589
|
+
在使用约定式路由的情况下,务必使用 `@modern-js/runtime/router` 中的 API,不直接使用 React Router 的 API。因为 Modern.js 内部会安装 React Router,如果应用中使用了 React Router 的 API,可能会导致两个版本的 React Router 同时存在,出现不符合预期的行为。
|
551
590
|
|
552
591
|
:::note
|
553
|
-
|
592
|
+
如果应用中必须直接使用 React Router 包的 API,例如部分路由行为被封装在统一的 npm 包中,那应用可以通过设置 [`source.alias`](/configure/app/source/alias),将 `react-router` 和 `react-router-dom` 统一指向项目的依赖,避免两个版本的 React Router 同时存在的问题。
|
554
593
|
:::
|
555
594
|
|
556
|
-
## 其他路由方案
|
557
|
-
|
558
|
-
默认情况下,Modern.js 会开启内置的路由方案,即 React Router。
|
559
|
-
|
560
|
-
```js
|
561
|
-
export default defineConfig({
|
562
|
-
runtime: {
|
563
|
-
router: true,
|
564
|
-
},
|
565
|
-
});
|
566
|
-
```
|
567
|
-
|
568
|
-
如上述配置,当开启 [`runtime.router`](/configure/app/runtime/router) 配置时,Modern.js 会从 `@modern-js/runtime/router` 命名空间导出 React Router 的 API 供开发者使用,保证开发者和 Modern.js 中使用同一份代码,并自动根据 router 配置包裹 `Provider` 组件。另外,这种情况下,React Router 的代码会被打包到 JS 产物中。
|
569
|
-
|
570
|
-
如果项目已经有自己的路由方案,或者不需要使用客户端路由,可以关闭这个功能。
|
571
|
-
|
572
|
-
```js
|
573
|
-
export default defineConfig({
|
574
|
-
runtime: {
|
575
|
-
router: false,
|
576
|
-
},
|
577
|
-
});
|
578
|
-
```
|
579
595
|
|
580
|
-
如上述配置, 如果关闭了 [`runtime.router`](/configure/app/runtime/router) 配置,并直接使用 `react-router-dom` 进行项目路由管理时,还需要根据 React Router 文档自行包裹 `Provider`。
|