@modern-js/main-doc 0.0.0-nightly-20231007160533 → 0.0.0-nightly-20231009160555

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.
@@ -15,7 +15,7 @@ Because the two pages need to share the same set of state (point of contact tabu
15
15
 
16
16
  Modern.js support obtaining data through Data Loader in `layout.tsx`, we first move the data acquisition part of the code to `src/routes/layout.tsx`:
17
17
 
18
- ```ts title="src/routes/layout.loader.ts"
18
+ ```ts title="src/routes/layout.data.ts"
19
19
  export type LoaderData = {
20
20
  code: number;
21
21
  data: {
@@ -25,7 +25,7 @@ export type LoaderData = {
25
25
  }[];
26
26
  };
27
27
 
28
- export default async (): Promise<LoaderData> => {
28
+ export const loader = async (): Promise<LoaderData> => {
29
29
  const data = new Array(20).fill(0).map(() => {
30
30
  const firstName = name.firstName();
31
31
  return {
@@ -58,7 +58,7 @@ import 'tailwindcss/base.css';
58
58
  import 'tailwindcss/components.css';
59
59
  import 'tailwindcss/utilities.css';
60
60
  import '../styles/utils.css';
61
- import type { LoaderData } from './layout.loader';
61
+ import type { LoaderData } from './layout.data';
62
62
 
63
63
  export default function Layout() {
64
64
  const { data } = useLoaderData() as LoaderData;
@@ -54,7 +54,7 @@ sidebar_position: 2
54
54
 
55
55
  在组件中,可以通过 [useParams](/apis/app/runtime/router/router#useparams) 获取对应命名的参数。
56
56
 
57
- 在使用 [loader](/guides/basic-features/data-fetch#loader-函数) 函数获取数据时,`params` 会作为 `loader` 函数的入参,通过 `params` 的属性可以获取到对应的参数。
57
+ 在使用 [loader](/guides/basic-features/data/data-fetch#loader-函数) 函数获取数据时,`params` 会作为 `loader` 函数的入参,通过 `params` 的属性可以获取到对应的参数。
58
58
 
59
59
  ## 布局组件
60
60
 
@@ -26,7 +26,7 @@ import InitRspackApp from '@site-docs/components/init-rspack-app';
26
26
  在使用 Rspack 作为打包工具时,由于部分能力尚在开发中,以下 features 暂时无法使用,我们将在未来提供支持:
27
27
 
28
28
  - Storybook 调试
29
- - 客户端渲染(CSR)使用 [useLoader](/guides/basic-features/data-fetch.html)
29
+ - 客户端渲染(CSR)使用 [useLoader](/guides/basic-features/data/data-fetch.html)
30
30
 
31
31
  :::
32
32
 
@@ -22,8 +22,8 @@ export default defineConfig({
22
22
 
23
23
  Modern.js 中提供了 Data Loader,方便开发者在 SSR、CSR 下同构的获取数据。每个路由模块,如 `layout.tsx` 和 `page.tsx` 都可以定义自己的 Data Loader:
24
24
 
25
- ```ts title="src/routes/page.loader.ts"
26
- export default () => {
25
+ ```ts title="src/routes/page.data.ts"
26
+ export const loader = () => {
27
27
  return {
28
28
  message: 'Hello World',
29
29
  };
@@ -48,7 +48,7 @@ Modern.js 打破传统的 SSR 开发模式,提供了用户无感的 SSR 开发
48
48
 
49
49
  1. 当以客户端路由的方式请求页面时,Modern.js 会发送一个 HTTP 请求,服务端接收到请求后执行页面对应的 Data Loader 函数,然后将执行结果作为请求的响应返回浏览器。
50
50
 
51
- 2. 使用 Data Loader 时,数据获取发生在渲染前,Modern.js 也仍然支持在组件渲染时获取数据。更多相关内容可以查看[数据获取](/guides/basic-features/data-fetch)。
51
+ 2. 使用 Data Loader 时,数据获取发生在渲染前,Modern.js 也仍然支持在组件渲染时获取数据。更多相关内容可以查看[数据获取](/guides/basic-features/data/data-fetch)。
52
52
 
53
53
  :::
54
54
 
@@ -147,8 +147,8 @@ SPR 利用预渲染与缓存技术,为 SSR 页面提供静态 Web 的响应性
147
147
 
148
148
  这里模拟一个使用 `useLoaderData` API 的组件,Data Loader 中的请求需要消耗 2s 时间。
149
149
 
150
- ```tsx title="page.loader.ts"
151
- export default async () => {
150
+ ```tsx title="page.data.ts"
151
+ export const loader = async () => {
152
152
  await new Promise((resolve, reject) => {
153
153
  setTimeout(() => {
154
154
  resolve(null);
@@ -297,9 +297,9 @@ export const loader = () => {
297
297
 
298
298
  上述两种方式,都会为开发者带来一些心智负担。在真实的业务中,我们发现大多数的 Node / Web 代码混用都出现在数据请求中。
299
299
 
300
- 因此,Modern.js 基于[嵌套路由](/guides/basic-features/routes)开发设计了[更简单的方案](/guides/basic-features/data-fetch)来分离 CSR 和 SSR 的代码。
300
+ 因此,Modern.js 基于[嵌套路由](/guides/basic-features/routes)开发设计了[更简单的方案](/guides/basic-features/data/data-fetch)来分离 CSR 和 SSR 的代码。
301
301
 
302
- 我们可以通过独立文件来分离**数据请求**与**组件代码**。在 `routes/page.tsx` 中编写组件逻辑,在 `routes/page.loader.ts` 中编写数据请求逻辑。
302
+ 我们可以通过独立文件来分离**数据请求**与**组件代码**。在 `routes/page.tsx` 中编写组件逻辑,在 `routes/page.data.ts` 中编写数据请求逻辑。
303
303
 
304
304
  ```ts title="routes/page.tsx"
305
305
  export default Page = () => {
@@ -307,9 +307,9 @@ export default Page = () => {
307
307
  }
308
308
  ```
309
309
 
310
- ```ts title="routes/page.loader.tsx"
310
+ ```ts title="routes/page.data.tsx"
311
311
  import fse from 'fs-extra';
312
- export default () => {
312
+ export const loader = () => {
313
313
  const file = fse.readFileSync('./myfile');
314
314
  return {
315
315
  ...
@@ -319,7 +319,7 @@ export default () => {
319
319
 
320
320
  ## 接口请求
321
321
 
322
- 在 SSR 中发起接口请求时,开发者有时自己封装了同构的请求工具。部分接口需要传递用户 Cookie,开发者可以通过 [`useRuntimeContext`](/guides/basic-features/data-fetch#route-loader) API 获取到请求头来实现。
322
+ 在 SSR 中发起接口请求时,开发者有时自己封装了同构的请求工具。部分接口需要传递用户 Cookie,开发者可以通过 [`useRuntimeContext`](/guides/basic-features/data/data-fetch#route-loader) API 获取到请求头来实现。
323
323
 
324
324
  需要注意的是,此时获取到的是 HTML 请求的请求头,不一定适用于接口请求,因此**千万不能**透传所有请求头。并且,一些后端接口,或是通用网关,会根据请求头中的信息做校验,全量透传容易出现各种难以排查的问题,推荐**按需透传**。
325
325
 
@@ -349,7 +349,7 @@ Modern.js 的流式渲染基于 React Router 实现,主要涉及 API 有:
349
349
 
350
350
  ### 异步获取数据
351
351
 
352
- ```ts title="page.loader.ts"
352
+ ```ts title="page.data.ts"
353
353
  import { defer, type LoaderFunctionArgs } from '@modern-js/runtime/router';
354
354
 
355
355
  interface User {
@@ -361,7 +361,7 @@ export interface Data {
361
361
  data: User;
362
362
  }
363
363
 
364
- export default ({ params }: LoaderFunctionArgs) => {
364
+ export const loader = ({ params }: LoaderFunctionArgs) => {
365
365
  const userId = params.id;
366
366
 
367
367
  const user = new Promise<User>(resolve => {
@@ -382,10 +382,10 @@ export default ({ params }: LoaderFunctionArgs) => {
382
382
 
383
383
  `defer` 还可以同时接收异步数据和同步数据。例如:
384
384
 
385
- ```ts title="page.loader.ts"
385
+ ```ts title="page.data.ts"
386
386
  // 省略部分代码
387
387
 
388
- export default ({ params }: LoaderFunctionArgs) => {
388
+ export const loader = ({ params }: LoaderFunctionArgs) => {
389
389
  const userId = params.id;
390
390
 
391
391
  const user = new Promise<User>(resolve => {
@@ -419,7 +419,7 @@ export default ({ params }: LoaderFunctionArgs) => {
419
419
  ```tsx title="page.tsx"
420
420
  import { Await, useLoaderData } from '@modern-js/runtime/router';
421
421
  import { Suspense } from 'react';
422
- import type { Data } from './page.loader';
422
+ import type { Data } from './page.data';
423
423
 
424
424
  const Page = () => {
425
425
  const data = useLoaderData() as Data;
@@ -452,7 +452,7 @@ export default Page;
452
452
  :::warning 注意
453
453
  从 Data Loader 文件导入类型时,需要使用 import type 语法,保证只导入类型信息,这样可以避免 Data Loader 的代码打包到前端产物的 bundle 文件中。
454
454
 
455
- 所以,这里的导入方式为:`import type { Data } from './page.loader'`;
455
+ 所以,这里的导入方式为:`import type { Data } from './page.data'`;
456
456
 
457
457
  :::
458
458
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "label": "数据管理",
3
+ "position": 3
4
+ }
@@ -11,16 +11,22 @@ Modern.js 中提供了开箱即用的数据获取能力,开发者可以通过
11
11
 
12
12
  ## Data Loader(推荐)
13
13
 
14
- Modern.js 推荐使用约定式路由做路由的管理,通过 Modern.js 的[约定式(嵌套)路由](/guides/basic-features/routes#约定式路由),每个路由组件(`layout.ts` 或 `page.ts`)可以有一个同名的 `loader` 文件,该 `loader` 文件需要导出一个函数,函数会在组件渲染之前执行,为路由组件提供数据。
14
+ Modern.js 推荐使用约定式路由做路由的管理,通过 Modern.js 的[约定式(嵌套)路由](/guides/basic-features/routes#约定式路由),每个路由组件(`layout.ts` 或 `page.ts`)可以有一个同名的 `data` 文件,该 `data` 文件可以导出一个 `loader` 函数,函数会在组件渲染之前执行,为路由组件提供数据。
15
15
 
16
16
  :::info
17
17
  Modern.js v1 支持通过 [useLoader](#useloader(旧版)) 获取数据,这已经不是我们推荐的用法,除迁移过程外,不推荐两者混用。
18
18
 
19
19
  :::
20
20
 
21
+ :::warning
22
+ - 在之前的版本中,Modern.js Data Loader 是定义在 `loader` 文件中的,在之后的版本中,我们推荐定义在 `data` 文件中,同时我们会保持对 `loader` 文件的兼容。
23
+ - 在 `data` 文件中,对应的 `loader` 需要具名导出。
24
+
25
+ :::
26
+
21
27
  ### 基础示例
22
28
 
23
- 路由组件如 `layout.ts` 或 `page.ts`,可以定义同名的 `loader` 文件,`loader` 文件中导出一个函数,该函数提供组件所需的数据,然后在路由组件中通过 `useLoaderData` 函数获取数据,如下面示例:
29
+ 路由组件如 `layout.ts` 或 `page.ts`,可以定义同名的 `data` 文件,`data` 文件中导出一个 `loader` 函数,该函数提供组件所需的数据,然后在路由组件中通过 `useLoaderData` 函数获取数据,如下面示例:
24
30
 
25
31
  ```bash
26
32
  .
@@ -28,16 +34,16 @@ Modern.js v1 支持通过 [useLoader](#useloader(旧版)) 获取数据,这
28
34
  ├── layout.tsx
29
35
  └── user
30
36
  ├── layout.tsx
31
- ├── layout.loader.ts
37
+ ├── layout.data.ts
32
38
  ├── page.tsx
33
- └── page.loader.ts
39
+ └── page.data.ts
34
40
  ```
35
41
 
36
42
  在文件中定义以下代码:
37
43
 
38
44
  ```ts title="routes/user/page.tsx"
39
45
  import { useLoaderData } from '@modern-js/runtime/router';
40
- import type { ProfileData } from './page.loader.ts';
46
+ import type { ProfileData } from './page.data.ts';
41
47
 
42
48
  export default function UserPage() {
43
49
  const profileData = useLoaderData() as ProfileData;
@@ -45,19 +51,19 @@ export default function UserPage() {
45
51
  }
46
52
  ```
47
53
 
48
- ```ts title="routes/user/page.loader.ts"
54
+ ```ts title="routes/user/page.data.ts"
49
55
  export type ProfileData = {
50
56
  /* some types */
51
57
  };
52
58
 
53
- export default async (): Promise<ProfileData> => {
59
+ export const loader = async (): Promise<ProfileData> => {
54
60
  const res = await fetch('https://api/user/profile');
55
61
  return await res.json();
56
62
  };
57
63
  ```
58
64
 
59
65
  :::caution
60
- 这里路由组件和 `loader` 文件共享类型,要使用 `import type` 语法。
66
+ 这里路由组件和 `data` 文件共享类型,要使用 `import type` 语法。
61
67
 
62
68
  :::
63
69
 
@@ -81,10 +87,10 @@ export default async (): Promise<ProfileData> => {
81
87
  当路由文件通过 `[]` 时,会作为[动态路由](/guides/basic-features/routes#动态路由),动态路由片段会作为参数传入 `loader` 函数:
82
88
 
83
89
  ```tsx
84
- // routes/user/[id]/page.loader.ts
90
+ // routes/user/[id]/page.data.ts
85
91
  import { LoaderFunctionArgs } from '@modern-js/runtime/router';
86
92
 
87
- export default async ({ params }: LoaderFunctionArgs) => {
93
+ export const loader = async ({ params }: LoaderFunctionArgs) => {
88
94
  const { id } = params;
89
95
  const res = await fetch(`https://api/user/${id}`);
90
96
  return res.json();
@@ -100,10 +106,10 @@ export default async ({ params }: LoaderFunctionArgs) => {
100
106
  一个常见的使用场景是通过 `request` 获取查询参数:
101
107
 
102
108
  ```tsx
103
- // routes/user/[id]/page.loader.ts
109
+ // routes/user/[id]/page.data.ts
104
110
  import { LoaderFunctionArgs } from '@modern-js/runtime/router';
105
111
 
106
- export default async ({ request }: LoaderFunctionArgs) => {
112
+ export const loader = async ({ request }: LoaderFunctionArgs) => {
107
113
  const url = new URL(request.url);
108
114
  const userId = url.searchParams.get('id');
109
115
  return queryUser(userId);
@@ -142,7 +148,7 @@ const loader = async (): Promise<ProfileData> => {
142
148
  Modern.js 对 `fetch` API 做了 polyfill, 用于发起请求,该 API 与浏览器的 `fetch` API 一致,但是在服务端也能使用该 API 发起请求,这意味着不管是 CSR 还是 SSR,都可以使用统一的 `fetch` API 进行数据获取:
143
149
 
144
150
  ```tsx
145
- async function loader() {
151
+ export async function loader() {
146
152
  const res = await fetch('https://api/user/profile');
147
153
  }
148
154
  ```
@@ -152,8 +158,8 @@ async function loader() {
152
158
  在 `loader` 函数中,可以通过 `throw error` 或者 `throw response` 的方式处理错误,当 `loader` 函数中有错误被抛出时,Modern.js 会停止执行当前 `loader` 中的代码,并将前端 UI 切换到定义的 [`ErrorBoundary`](/guides/basic-features/routes#错误处理) 组件:
153
159
 
154
160
  ```tsx
155
- // routes/user/profile/page.loader.ts
156
- export default async function loader() {
161
+ // routes/user/profile/page.data.ts
162
+ export async function loader() {
157
163
  const res = await fetch('https://api/user/profile');
158
164
  if (!res.ok) {
159
165
  throw res;
@@ -184,8 +190,8 @@ export default ErrorBoundary;
184
190
  // routes/user/profile/page.tsx
185
191
  import { useRouteLoaderData } from '@modern-js/runtime/router';
186
192
 
187
- export default function UserLayout() {
188
- // 获取 routes/user/layout.loader.ts 中 `loader` 返回的数据
193
+ export function UserLayout() {
194
+ // 获取 routes/user/layout.data.ts 中 `loader` 返回的数据
189
195
  const data = useRouteLoaderData('user/layout');
190
196
  return (
191
197
  <div>
@@ -220,12 +226,12 @@ export default function UserLayout() {
220
226
 
221
227
  :::
222
228
 
223
- 创建 `user/layout.loader.ts`,并添加以下代码:
229
+ 创建 `user/layout.data.ts`,并添加以下代码:
224
230
 
225
- ```ts title="routes/user/layout.loader.ts"
231
+ ```ts title="routes/user/layout.data.ts"
226
232
  import { defer } from '@modern-js/runtime/router';
227
233
 
228
- const loader = () =>
234
+ export const loader = () =>
229
235
  defer({
230
236
  userInfo: new Promise(resolve => {
231
237
  setTimeout(() => {
@@ -236,8 +242,6 @@ const loader = () =>
236
242
  }, 1000);
237
243
  }),
238
244
  });
239
-
240
- export default loader;
241
245
  ```
242
246
 
243
247
  在 `user/layout.tsx` 中添加以下代码:
@@ -298,12 +302,12 @@ export default () => {
298
302
 
299
303
  ```ts
300
304
  // This won't work!
301
- export default async () => {
305
+ export const loader = async () => {
302
306
  const res = fetch('https://api/user/profile');
303
307
  return res.json();
304
308
  };
305
309
 
306
- import loader from './page.loader.ts';
310
+ import { loader } from './page.data.ts';
307
311
  export default function RouteComp() {
308
312
  const data = loader();
309
313
  }
@@ -315,7 +319,7 @@ export default function RouteComp() {
315
319
  // Not allowed
316
320
  // routes/layout.tsx
317
321
  import { useLoaderData } from '@modern-js/runtime/router';
318
- import { ProfileData } from './page.loader.ts'; // should use "import type" instead
322
+ import { ProfileData } from './page.data.ts'; // should use "import type" instead
319
323
 
320
324
  export const fetch = wrapFetch(fetch);
321
325
 
@@ -324,13 +328,13 @@ export default function UserPage() {
324
328
  return <div>{profileData}</div>;
325
329
  }
326
330
 
327
- // routes/layout.loader.ts
331
+ // routes/layout.data.ts
328
332
  import { fetch } from './layout.tsx'; // should not be imported from the routing component
329
333
  export type ProfileData = {
330
334
  /* some types */
331
335
  };
332
336
 
333
- export default async (): Promise<ProfileData> => {
337
+ export const loader = async (): Promise<ProfileData> => {
334
338
  const res = await fetch('https://api/user/profile');
335
339
  return await res.json();
336
340
  };
@@ -0,0 +1,236 @@
1
+ ---
2
+ title: 数据写入
3
+ sidebar_position: 4
4
+ ---
5
+
6
+ # 数据写入
7
+
8
+ 在 Data Loader 章节中,介绍了 Modern.js 获取数据的方式,你可能会遇到两个问题:
9
+ 1. 如何更新 Data Loader 中的数据?
10
+ 2. 如何将新的数据传递到服务器?
11
+
12
+ EdenX 对此的解决方案是 DataAction。
13
+
14
+ ## 基本示例
15
+
16
+ Data Action 和 Data Loader 一样,也是基于约定式路由的,通过 Modern.js 的[约定式(嵌套)路由](/guides/basic-features/routes#约定式路由),每个路由组件(`layout.ts`,`page.ts` 或 `$.tsx`)可以有一个同名的 `data` 文件,`data` 文件中可以导出一个命名为 `action` 的函数。
17
+ ```bash
18
+ .
19
+ └── routes
20
+ └── user
21
+ ├── layout.tsx
22
+ └── layout.data.ts
23
+ ```
24
+ 在文件中定义以下代码:
25
+ ```ts title="routes/user/layout.data.ts"
26
+ import type { ActionFunction } from '@modern-js/runtime/router';
27
+
28
+ export const action: ActionFunction = ({ request }) => {
29
+ const newUser = await request.json();
30
+ const name = newUser.name;
31
+ return updateUserProfile(name);
32
+ }
33
+ ```
34
+
35
+ ```tsx title="routes/user/layout.tsx"
36
+ import {
37
+ useFetcher,
38
+ useLoaderData,
39
+ useParams,
40
+ Outlet
41
+ } from '@modern-js/runtime/router';
42
+
43
+ export default () => {
44
+ const userInfo = useLoaderData();
45
+ const { submit } = useFetcher();
46
+ const editUser = () => {
47
+ const newUser = {
48
+ name: 'Modern.js'
49
+ }
50
+ return submit(newUser, {
51
+ method: 'post',
52
+ encType: 'application/json',
53
+ })
54
+ }
55
+ return (
56
+ <div>
57
+ <button onClick={editUser}>edit user</button>
58
+ <div className="user-profile">
59
+ {userInfo}
60
+ </div>
61
+ <Outlet context={userInfo}></Outlet>
62
+ </div>
63
+ )
64
+ }
65
+ ```
66
+
67
+ 这里当执行 submit 后,会触发定义的 action 函数;在 action 函数中,可以通过 request (request.json,request.formData)获取提交的数据,获取数据后,再发送数据到服务端。
68
+
69
+ 而 action 函数执行完,会执行 loader 函数代码,并更新对应的数据和视图。
70
+
71
+ ![action flow](https://lf3-static.bytednsdoc.com/obj/eden-cn/ulkl/ljhwZthlaukjlkulzlp/action-flow.png)
72
+
73
+
74
+
75
+ ## 为什么要提供 Data Action
76
+
77
+ Modern.js 中提供 Data Action 主要是为了使 UI 和服务器的状态能保持同步,通过这种方式可以减少状态管理的负担,
78
+ 传统的状态管理方式,会在客户端和远程分别持有状态:
79
+
80
+ ![traditional state manage](https://lf3-static.bytednsdoc.com/obj/eden-cn/ulkl/ljhwZthlaukjlkulzlp/action-state-manage.png)
81
+
82
+ 而在 Modern.js 中,我们希望通过 Loader 和 Action 帮助开发者自动的同步客户端和服务端的状态:
83
+
84
+ ![state manage](https://lf3-static.bytednsdoc.com/obj/eden-cn/ulkl/ljhwZthlaukjlkulzlp/action-state-manage1.png)
85
+
86
+ 如果项目中组件共享的数据主要服务端的状态,则无需在项目引入客户端状态管理库,使用 Data Loader 请求数据,通过 [`useRouteLoaderData`](/guides/basic-features/data/data-fetch.md) 在子组件中共享数据,
87
+ 通过 Data Actino 修改和同步服务端的状态。
88
+
89
+
90
+
91
+ ## `action` 函数
92
+
93
+ 与 `loader` 函数一样,`action` 函数有两个入参,`params` 和 `request`:
94
+
95
+ ### `params`
96
+
97
+ 当路由文件通过 `[]` 时,会作为[动态路由](/guides/basic-features/routes#动态路由),动态路由片段会作为参数传入 `action` 函数:
98
+
99
+ ```tsx
100
+ // routes/user/[id]/page.data.ts
101
+ import { ActionFunctionArgs } from '@modern-js/runtime/router';
102
+
103
+ export const action = async ({ params }: ActionFunctionArgs) => {
104
+ const { id } = params;
105
+ const res = await fetch(`https://api/user/${id}`);
106
+ return res.json();
107
+ };
108
+ ```
109
+
110
+ 当访问 `/user/123` 时,`action` 函数的参数为 `{ params: { id: '123' } }`。
111
+
112
+
113
+ ### `request`
114
+
115
+ `request` 是一个 [Fetch Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) 实例。
116
+
117
+ 通过 `request`,可以在 action 函数中获取到客户端提交的数据,如 `request.json()`,`request.formData()`,`request.json()` 等,
118
+ 具体应该使用哪个 API,请参考[数据类型](#数据类型)。
119
+
120
+ ```tsx
121
+ // routes/user/[id]/page.data.ts
122
+ import { ActionFunctionArgs } from '@modern-js/runtime/router';
123
+
124
+ export const action = async ({ request }: ActionFunctionArgs) => {
125
+ const newUser = await request.json();
126
+ return updateUser(newUser);
127
+ };
128
+ ```
129
+
130
+ ### 返回值
131
+
132
+ `action` 函数的返回值可以是任何可序列化的内容,也可以是一个 [Fetch Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) 实例,
133
+ 可以通过 [`useActionData`](https://reactrouter.com/en/main/hooks/use-action-data) 访问 response 中内容。
134
+
135
+
136
+ ## useSubmit 和 useFetcher
137
+
138
+ ### 区别
139
+
140
+ 你可以通过 [`useSubmit`](https://reactrouter.com/en/main/hooks/use-submit) 或 [`useFetcher`](https://reactrouter.com/en/main/hooks/use-fetcher) 调用 action,它们的区别是通过
141
+ `useSubmit` 调用 action,会触发浏览器的导航,通过 `useFetcher` 则不会触发浏览器的导航。
142
+
143
+ useSubmit:
144
+
145
+ ```ts
146
+ const submit = useSubmit();
147
+ submit(null, { method: "post", action: "/logout" });
148
+ ```
149
+
150
+ useFetcher:
151
+ ```ts
152
+ const { submit } = useFetcher();
153
+ submit(null, { method: "post", action: "/logout" });
154
+ ```
155
+
156
+ `submit` 函数有两个入参,`method` 和 `action`,`method` 相当于表单提交时的 `method`,大部分写入数据的场景下,`method` 可以传入 `post`,
157
+ `action` 用来指定触发哪个路由组件的 `action`,如果未传入 `action` 入参,默认会触发当前路由组件的 action,即 `user/page.tsx` 组件或子组件中执行 submit,
158
+ 会触发 `user/page.data.ts` 中定义的 action。
159
+
160
+ :::info
161
+ 这两个 API 更多的信息可参考相关文档:
162
+ - [`useSubmit`](https://reactrouter.com/en/main/hooks/use-submit)
163
+ - [`useFetcher`](https://reactrouter.com/en/main/hooks/use-fetcher)
164
+
165
+ :::
166
+
167
+
168
+ ### 数据类型
169
+
170
+ `submit` 函数的第一个入参,可以接受不同类型的值。
171
+ 如 `FormData`:
172
+ ```ts
173
+ let formData = new FormData();
174
+ formData.append("cheese", "gouda");
175
+ submit(formData);
176
+ // In the action, you can get the data by request.json
177
+ ```
178
+
179
+ 或 `URLSearchParams` 类型的值:
180
+ ```ts
181
+ let searchParams = new URLSearchParams();
182
+ searchParams.append("cheese", "gouda");
183
+ submit(searchParams);
184
+ // In the action, you can get the data by request.json
185
+ ```
186
+
187
+ 或任意 `URLSearchParams` 构造函数可接受的值
188
+ ```ts
189
+ submit("cheese=gouda&toasted=yes");
190
+ submit([
191
+ ["cheese", "gouda"],
192
+ ["toasted", "yes"],
193
+ ]);
194
+ // In the action, you can get the data by request.json
195
+ ```
196
+
197
+ 默认情况下,如果 `submit` 函数中的第一个入参是一个对象,对应的数据会被 encode 为 `formData`:
198
+
199
+ ```ts
200
+ submit(
201
+ { key: "value" },
202
+ {
203
+ method: "post",
204
+ encType: "application/x-www-form-urlencoded",
205
+ }
206
+ );
207
+
208
+ // In the action, you can get the data by request.formData
209
+ ```
210
+
211
+ 也可以指定为 json 编码:
212
+
213
+ ```tsx
214
+ submit(
215
+ { key: "value" },
216
+ { method: "post", encType: "application/json" }
217
+ );
218
+
219
+ submit('{"key":"value"}', {
220
+ method: "post",
221
+ encType: "application/json",
222
+ });
223
+
224
+ // In the action, you can get the data by request.json
225
+ ```
226
+
227
+ 或提交纯文本:
228
+ ```ts
229
+ submit("value", { method: "post", encType: "text/plain" });
230
+ // In the action, you can get the data by request.text
231
+ ```
232
+
233
+
234
+ ## CSR 和 SSR
235
+
236
+ 与 Data Loader 一样,SSR 项目中,Data Action 是在服务端执行的(框架会自动发请求触发 Data Action),而在 CSR 项目中,Data Action 是在客户端执行的。
@@ -164,7 +164,7 @@ export const handle = {
164
164
 
165
165
  ```ts title="routes/layout.ts"
166
166
  export default () => {
167
- const matches = useMatches;
167
+ const matches = useMatches();
168
168
  const breadcrumbs = matches.map(
169
169
  matchedRoute => matchedRoute?.handle?.breadcrumbName,
170
170
  );
@@ -189,7 +189,7 @@ export default () => {
189
189
 
190
190
  在组件中,可以通过 [useParams](/apis/app/runtime/router/router#useparams) 获取对应命名的参数。
191
191
 
192
- 在 loader 中,params 会作为 [loader](/guides/basic-features/data-fetch#loader-函数) 的入参,通过 `params.xxx` 可以获取。
192
+ 在 loader 中,params 会作为 [loader](/guides/basic-features/data/data-fetch#loader-函数) 的入参,通过 `params.xxx` 可以获取。
193
193
 
194
194
  ### 动态可选路由
195
195
 
@@ -209,7 +209,7 @@ export default () => {
209
209
 
210
210
  在组件中,可以通过 [useParams](/apis/app/runtime/router/router#useparams) 获取对应命名的参数。
211
211
 
212
- 在 loader 中,params 会作为 [loader](/guides/basic-features/data-fetch#loader-函数) 的入参,通过 `params.xxx` 可以获取。
212
+ 在 loader 中,params 会作为 [loader](/guides/basic-features/data/data-fetch#loader-函数) 的入参,通过 `params.xxx` 可以获取。
213
213
 
214
214
  ### 通配路由
215
215
 
@@ -352,12 +352,12 @@ Modern.js 建议必须有根 Layout 和根 Loading。
352
352
 
353
353
  ### 路由重定向
354
354
 
355
- 可以通过创建 [`Data Loader`](/guides/basic-features/data-fetch) 文件做路由的重定向,如有文件 `routes/user/page.tsx`,想对这个文件对应的路由做重定向,可以创建 `routes/user/page.loader.ts` 文件:
355
+ 可以通过创建 [`Data Loader`](/guides/basic-features/data/data-fetch) 文件做路由的重定向,如有文件 `routes/user/page.tsx`,想对这个文件对应的路由做重定向,可以创建 `routes/user/page.data.ts` 文件:
356
356
 
357
- ```ts title="routes/user/page.loader.ts"
357
+ ```ts title="routes/user/page.data.ts"
358
358
  import { redirect } from '@modern-js/runtime/router';
359
359
 
360
- export default () => {
360
+ export const loader () => {
361
361
  const user = await getUser();
362
362
  if (!user) {
363
363
  return redirect('/login');
@@ -504,7 +504,7 @@ export const init = (context: RuntimeContext) => {
504
504
  :::info
505
505
 
506
506
  - 该功能目前仅在 Webpack 项目中支持,Rspack 项目暂不支持。
507
- - 对数据的预加载目前只会预加载 SSR 项目中 [Data Loader](/guides/basic-features/data-fetch) 中返回的数据。
507
+ - 对数据的预加载目前只会预加载 SSR 项目中 [Data Loader](/guides/basic-features/data/data-fetch) 中返回的数据。
508
508
 
509
509
  :::
510
510
 
@@ -18,7 +18,7 @@ pnpm add faker@5
18
18
  pnpm add @types/faker@5 -D
19
19
  ```
20
20
 
21
- 创建 `src/routes/page.loader.ts`:
21
+ 创建 `src/routes/page.data.ts`:
22
22
 
23
23
  ```tsx
24
24
  import { name, internet } from 'faker';
@@ -32,7 +32,7 @@ type LoaderData = {
32
32
  }[];
33
33
  };
34
34
 
35
- export default async (): Promise<LoaderData> => {
35
+ export const loader = async (): Promise<LoaderData> => {
36
36
  const data = new Array(20).fill(0).map(() => {
37
37
  const firstName = name.firstName();
38
38
  return {
@@ -192,9 +192,9 @@ const Item = ({
192
192
  export default Item;
193
193
  ```
194
194
 
195
- 接下来,我们修改 `src/routes/page.tsx` 和 `src/routes/page.loader.ts`,为 `<Item>` 组件传递更多参数:
195
+ 接下来,我们修改 `src/routes/page.tsx` 和 `src/routes/page.data.ts`,为 `<Item>` 组件传递更多参数:
196
196
 
197
- ```ts title="src/routes/page.loader.ts"
197
+ ```ts title="src/routes/page.data.ts"
198
198
  export type LoaderData = {
199
199
  code: number;
200
200
  data: {
@@ -204,7 +204,7 @@ export type LoaderData = {
204
204
  }[];
205
205
  };
206
206
 
207
- export default async (): Promise<LoaderData> => {
207
+ export const loader async (): Promise<LoaderData> => {
208
208
  const data = new Array(20).fill(0).map(() => {
209
209
  const firstName = name.firstName();
210
210
  return {