@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.
Files changed (39) hide show
  1. package/docs/en/apis/app/runtime/core/use-loader.mdx +1 -1
  2. package/docs/en/components/ssr-monitor.mdx +3 -0
  3. package/docs/en/configure/app/output/ssg.mdx +52 -141
  4. package/docs/en/guides/advanced-features/_meta.json +0 -7
  5. package/docs/en/guides/basic-features/_meta.json +7 -1
  6. package/docs/en/guides/basic-features/data/data-fetch.mdx +134 -235
  7. package/docs/en/guides/basic-features/data/data-write.mdx +66 -77
  8. package/docs/en/guides/basic-features/render/_meta.json +1 -0
  9. package/docs/en/guides/basic-features/render/ssg.mdx +208 -0
  10. package/docs/en/guides/{advanced-features/ssr/cache.mdx → basic-features/render/ssr-cache.mdx} +38 -50
  11. package/docs/en/guides/basic-features/render/ssr.mdx +301 -0
  12. package/docs/en/guides/basic-features/render/streaming-ssr.mdx +230 -0
  13. package/docs/en/guides/basic-features/routes.mdx +275 -263
  14. package/docs/en/guides/concept/entries.mdx +9 -2
  15. package/docs/zh/apis/app/runtime/core/use-loader.mdx +1 -1
  16. package/docs/zh/components/ssr-monitor.mdx +3 -0
  17. package/docs/zh/configure/app/output/ssg.mdx +49 -139
  18. package/docs/zh/guides/advanced-features/_meta.json +0 -7
  19. package/docs/zh/guides/basic-features/_meta.json +7 -1
  20. package/docs/zh/guides/basic-features/data/data-fetch.mdx +98 -213
  21. package/docs/zh/guides/basic-features/data/data-write.mdx +54 -55
  22. package/docs/zh/guides/basic-features/render/_meta.json +1 -0
  23. package/docs/zh/guides/basic-features/render/ssg.mdx +210 -0
  24. package/docs/zh/guides/{advanced-features/ssr/cache.mdx → basic-features/render/ssr-cache.mdx} +16 -26
  25. package/docs/zh/guides/basic-features/render/ssr.mdx +309 -0
  26. package/docs/zh/guides/{advanced-features/ssr/stream.mdx → basic-features/render/streaming-ssr.mdx} +22 -37
  27. package/docs/zh/guides/basic-features/routes.mdx +252 -237
  28. package/docs/zh/guides/concept/entries.mdx +6 -3
  29. package/package.json +6 -6
  30. package/docs/en/guides/advanced-features/ssg.mdx +0 -116
  31. package/docs/en/guides/advanced-features/ssr/_meta.json +0 -1
  32. package/docs/en/guides/advanced-features/ssr/index.mdx +0 -23
  33. package/docs/en/guides/advanced-features/ssr/stream.mdx +0 -248
  34. package/docs/en/guides/advanced-features/ssr/usage.mdx +0 -341
  35. package/docs/en/guides/advanced-features/ssr.mdx +0 -555
  36. package/docs/zh/guides/advanced-features/ssg.mdx +0 -116
  37. package/docs/zh/guides/advanced-features/ssr/_meta.json +0 -1
  38. package/docs/zh/guides/advanced-features/ssr/index.mdx +0 -23
  39. 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),并提供了多种类型的路由模式。根据不同 [入口](/guides/concept/entries) 类型,将路由分为三种模式,分别是**约定式路由**,**自控式路由**和**其他路由方案**。
7
+ Modern.js 的路由基于 [React Router 6](https://reactrouter.com/en/main),提供了基于文件约定的路由能力,并支持了业界流行的约定式路由模式:**嵌套路由**。当入口被识别为 [约定式路由](/guides/concept/entries.html#约定式路由) 时,Modern.js 会自动基于文件系统,生成对应的路由结构。
8
8
 
9
9
  :::note
10
- 本小节提到的路由,都是客户端路由,即 SPA 路由。
11
-
10
+ 本小节提到的路由,都是约定式路由。
12
11
  :::
13
12
 
14
- ## 约定式路由
13
+ ## 什么是嵌套路由
14
+
15
+ 嵌套路由是一种将 URL 分段与组件层次结构和数据耦合起来的路由模式。通常,URL 段会决定:
15
16
 
16
- `routes/` 为约定的入口,Modern.js 会自动基于文件系统,生成对应的路由结构。
17
+ - 页面上要渲染的布局
18
+ - 这些布局的数据依赖
17
19
 
18
- Modern.js 支持了业界流行的约定式路由模式:**嵌套路由**,使用嵌套路由时,页面的路由 与 UI 结构是相呼应的,我们将会详细介绍这种路由模式。
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
- 在`routes/` 目录下,目录名会作为路由 url 的映射,Modern.js 有两个文件约定 `layout.[jt]sx` 和 `page.[jt]sx`(后面简写为 `.tsx`)。这两个文件决定了应用的布局层次,其中 `layout.tsx` 中作为布局组件,`page.tsx` 作为内容组件,是整条路由的叶子节点(一条路由有且仅有一个叶子节点,且必须以叶子节点结尾)。
34
-
35
- 例如以下目录结构:
35
+ 在 `routes/` 目录下,子目录名会作为路由 URL 的映射,Modern.js 有两个文件约定 `layout.tsx` 和 `page.tsx`。这两个文件决定了应用的布局层次,其中:
36
36
 
37
- ```bash
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
- 当添加 `layout.tsx` 后, 假设有以下目录
46
+ `<Page>` 组件是指 `routes/` 目录下所有 `page.tsx` 文件,也是所有路由的叶子组件。除了通配路由外,任何路由都应该由 `<Page>` 组件结束。
51
47
 
52
- :::info
53
- 这里 `routes/layout.tsx` 会作为 `/` 路由下所有组件的布局组件使用, `routes/user/layout.tsx` 会作为 `/user` 路由下所有路由组件的布局组件使用。
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
- 当路由为 `/` 时,会有以下 UI 布局:
68
-
69
- ```tsx
70
- <Layout>
71
- <Page />
72
- </Layout>
73
- ```
64
+ 会产出下面两条路由:
74
65
 
75
- 同样,`routes/user/layout.tsx` 会作为 `/user` 路由下所有组件的布局组件使用。当路由为 `/user` 时, 会有以下 UI 布局:
66
+ - `/`
67
+ - `/user`
76
68
 
77
- ```tsx
78
- <Layout>
79
- <UserLayout>
80
- <UserPage />
81
- </UserLayout>
82
- </Layout>
83
- ```
84
69
 
85
- #### Layout
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 中新的 API,详情可以查看 [Outlet](https://reactrouter.com/en/main/components/outlet#outlet).
103
-
87
+ `<Outlet>` 是 React Router 6 中提供的 API,详情可以查看 [Outlet](https://reactrouter.com/en/main/components/outlet#outlet)
104
88
  :::
105
89
 
106
- 为了方便介绍 `<Layout>` 与 `<Outlet>` 的关系,以下面的文件目录举例:
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` 中导出的组件,生成以下 UI 结构:
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` 中导出的组件,生成以下 UI 结构:
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` 中导出的组件。生成以下 UI 结构:
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
- #### Page
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
- └── page.tsx
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
- 在 loader 中,params 会作为 [loader](/guides/basic-features/data/data-fetch#loader-函数) 的入参,通过 `params.xxx` 可以获取。
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
- ├── user
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
- 在组件中,可以通过 [useParams](/apis/app/runtime/router/router#useparams) 获取对应命名的参数。
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
- 如果在 routes 目录下创建 `$.tsx` 文件,该文件会作为通配路由组件,当没有匹配的路由时,会渲染该路由组件。
191
+ 如果在某个子目录下存在 `$.tsx` 文件,该文件会作为通配路由组件,当没有匹配的路由时,会渲染该路由组件。
217
192
 
218
193
  :::note
219
- `$.tsx` 可以认为是一种特殊的 `page` 路由组件,当前目录下有 `layout` 组件时,`$.tsx`,会作为 `layout` 的子组件渲染。
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
- └── layout.tsx
209
+ ├── layout.tsx
230
210
  └── page.tsx
231
211
  ```
232
212
 
233
- 当访问任何匹配不到的路径时(如 `/blog/a`),都会渲染 `routes/blog/$.tsx` 组件,因为这里有 `layout.tsx`,渲染的 UI 如下:
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
- // 当 path 是 `/aaa/bbb` 时
250
- const params = useParams();
251
- params['*']; // => 'aaa/bbb'
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
- `$.tsx` 可以加入到 `routes` 目录下的任意目录中,一个常见的使用示例是添加 `routes/$.tsx` 文件去定制任意层级的 404 内容。
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
- │ └── signup
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` 和 `signup/page.tsx` 的布局组件,但`__auth` 不会作为路由路径片段。
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
- <UserProfileEdit /> // routes/user.profile.[id].edit/page.tsx
329
+ {/* routes/user.profile.[id].edit/page.tsx */}
330
+ <UserProfileEdit />
296
331
  </RootLayout>
297
332
  ```
298
333
 
299
- ### Loading (Experimental)
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
- `routes/` 下每一层目录中,开发者可以创建 `loading.tsx` 文件,默认导出一个 `<Loading>` 组件。
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
- 当路由目录下存在该组件和 `layout` 组件时,这一级子路由下所有的路由切换时,都会以该 `<Loading>` 组件作为 JS Chunk 加载时的 Fallback UI。例如以下文件目录:
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
- :::info
344
- 当目录的 Layout 组件不存在时,该目录下的 Loading 组件也不会生效。
345
- Modern.js 建议必须有根 Layout 和根 Loading。
346
-
347
- :::
348
-
349
- 当路由从 `/` 跳转到 `/blog` 时,如果 `blog/page` 组件的 JS Chunk 还未加载,则会先展示 `loading.tsx` 中导出的组件 UI。
455
+ ## 预加载
350
456
 
351
- 同理,当路由从 `/` 或者 `/blog` 跳转到 `/blog/123` 时,如果 `blog/[id]/page` 组件的 JS Chunk 还未加载,也会先展示 `loading.tsx` 中导出的组件 UI。
457
+ 大多数切换路由时白屏的情况,都可以通过定义 `<Loading>` 组件来优化体验。Modern.js 也支持在 `<Link>` 组件上定义 `prefetch` 属性,提前对静态资源和数据进行加载。
352
458
 
353
- ### 路由重定向
459
+ 对于有更高性能要求的应用,预加载可以进一步提升用户体验,减少展示 `<Loading>` 组件的时间:
354
460
 
355
- 可以使用 [`Data Loader`](/guides/basic-features/data/data-fetch) 文件做路由的重定向。如有文件 `routes/user/page.tsx`,想对这个文件对应的路由做重定向,可以创建 `routes/user/page.data.ts` 文件:
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
- 在组件内做重定向,则可以通过 `useNavigate` hook,示例如下:
465
+ :::tip
370
466
 
371
- ```ts title="routes/user/page.ts"
372
- import { useNavigate } from '@modern-js/runtime/router';
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
- `routes/` 下每一层目录中,开发者同样可以定义一个 `error.tsx` 文件,默认导出一个 `<ErrorBoundary>` 组件。
472
+ `prefetch` 属性有三个可选值:
383
473
 
384
- 当有路由目录下存在该组件时,组件渲染出错会被 `ErrorBoundary` 组件捕获。当目录未定义 `layout.tsx` 文件时,`<ErrorBoundary>` 组件不会生效。
474
+ - `none`, 默认值,不会做 prefetch,没有任何额外的行为。
475
+ - `intent`,这是我们推荐大多数场景下使用的值,当你把鼠标放在 Link 上时,会自动开始加载对应的分片和 Data Loader 中定义的数据,当鼠标移开时,会自动取消加载。在我们的测试中,即使是快速点击,也能减少大约 200ms 的加载时间。
476
+ - `render`,当 `<Link>` 组件渲染时,就会加载对应的分片和 Data Loader 中定义的数据。
385
477
 
386
- `<ErrorBoundary>` 可以返回出错时的 UI 视图,当前层级未声明 `<ErrorBoundary>` 组件时,错误会向上冒泡到更上层的组件,直到被捕获或抛出错误。同时,当组件出错时,只会影响捕获到该错误的路由组件及子组件,其他组件的状态和视图不受影响,可以继续交互。
478
+ :::details 值为 render 和不做路由分片的区别
479
+ - `render` 加载路由分片的时机是可控的,只有在 `<Link>` 组件进入视窗时才触发,可以通过控制 `<Link>` 组件的渲染位置来控制分片加载时机。
480
+ - `render` 仅在空闲时对静态资源进行加载,不占用重要模块的加载时间。
481
+ - 除了预加载路由分片,`render` 在 SSR 项目中还会发起数据预取。
387
482
 
388
- {/* Todo API 路由 */}
483
+ :::
389
484
 
390
- 在 `<ErrorBoundary>` 组件内,可以使用 [useRouteError](/apis/app/runtime/router/router#userouteerror) 获取的错误的具体信息:
391
485
 
392
- ```tsx
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
- 在每个根 `Layout` 组件中(`routes/layout.ts`),可以动态地定义运行时配置:
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
- import Practice from '@site-docs/components/routes-practice';
581
+ ## 常见问题
530
582
 
531
- <Practice />
583
+ 1. 为什么要提供 `@modern-js/runtime/router` 来导出 React Router API ?
532
584
 
533
- ## 自控式路由
585
+ 可以发现,在文档中所有的代码用例都是使用 `@modern-js/runtime/router` 包导出的 API,而不是直接使用 React Router 包导出的 API。那两者有什么区别呢?
534
586
 
535
- `src/App.tsx` 为约定的入口,Modern.js 不会对路由做额外的操作,开发者可以自行使用 [React Router 6](https://reactrouter.com/en/main) API 进行开发,例如:
587
+ 首先,在 `@modern-js/runtime/router` 中导出的 API React Router 包的 API 是完全一致的,如果某个 API 使用出现问题,请先检查 React Router 的文档和 Issues。
536
588
 
537
- ```ts title="src/App.tsx"
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
- Modern.js 默认对约定式路由做了一系列资源加载及渲染上的优化,并且提供了开箱即用的 SSR 能力。而在使用自控路由时,这些能力都需要开发者自行封装。我们推荐开发者使用约定式路由。
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`。