@modern-js/main-doc 2.35.1 → 2.36.0
Sign up to get free protection for your applications and to get access to all the features.
- package/docs/en/apis/app/hooks/src/routes.mdx +1 -1
- package/docs/en/components/debug-app.mdx +1 -1
- package/docs/en/components/turtorials-example-list.mdx +2 -0
- package/docs/en/configure/app/source/entries.mdx +7 -6
- package/docs/en/guides/advanced-features/bff/sdk.mdx +119 -0
- package/docs/en/guides/advanced-features/rspack-start.mdx +1 -1
- package/docs/en/guides/advanced-features/ssr.mdx +17 -17
- package/docs/en/guides/basic-features/data/_category_.json +4 -0
- package/docs/en/guides/basic-features/{data-fetch.mdx → data/data-fetch.mdx} +35 -27
- package/docs/en/guides/basic-features/data/data-write.mdx +241 -0
- package/docs/en/guides/basic-features/routes.mdx +7 -7
- package/docs/en/guides/concept/entries.mdx +31 -30
- package/docs/en/guides/get-started/quick-start.mdx +1 -1
- package/docs/en/guides/topic-detail/framework-plugin/extend.mdx +0 -1
- package/docs/en/guides/topic-detail/framework-plugin/hook-list.mdx +0 -1
- package/docs/en/guides/topic-detail/framework-plugin/hook.mdx +1 -1
- package/docs/en/guides/topic-detail/framework-plugin/implement.mdx +1 -2
- package/docs/en/guides/topic-detail/framework-plugin/introduction.mdx +1 -1
- package/docs/en/guides/topic-detail/framework-plugin/lifecycle.mdx +1 -1
- package/docs/en/guides/topic-detail/framework-plugin/plugin-api.mdx +31 -11
- package/docs/en/guides/topic-detail/framework-plugin/relationship.mdx +2 -2
- package/docs/en/guides/topic-detail/generator/new/config.md +0 -5
- package/docs/en/guides/topic-detail/micro-frontend/c03-main-app.mdx +2 -2
- package/docs/en/tutorials/first-app/c05-loader.mdx +2 -2
- package/docs/en/tutorials/first-app/c06-model.mdx +4 -4
- package/docs/en/tutorials/first-app/c07-container.mdx +3 -3
- package/docs/en/tutorials/foundations/introduction.mdx +3 -2
- package/docs/zh/apis/app/hooks/src/routes.mdx +1 -1
- package/docs/zh/components/debug-app.mdx +1 -1
- package/docs/zh/components/micro-runtime-config.mdx +2 -2
- package/docs/zh/components/turtorials-example-list.mdx +2 -0
- package/docs/zh/configure/app/source/entries.mdx +7 -6
- package/docs/zh/guides/advanced-features/bff/sdk.mdx +119 -0
- package/docs/zh/guides/advanced-features/rspack-start.mdx +1 -1
- package/docs/zh/guides/advanced-features/ssr.mdx +16 -16
- package/docs/zh/guides/basic-features/data/_category_.json +4 -0
- package/docs/zh/guides/basic-features/{data-fetch.mdx → data/data-fetch.md} +31 -27
- package/docs/zh/guides/basic-features/data/data-write.mdx +236 -0
- package/docs/zh/guides/basic-features/routes.mdx +7 -7
- package/docs/zh/guides/concept/entries.mdx +34 -32
- package/docs/zh/guides/get-started/quick-start.mdx +1 -1
- package/docs/zh/guides/topic-detail/framework-plugin/extend.mdx +0 -1
- package/docs/zh/guides/topic-detail/framework-plugin/hook-list.mdx +0 -1
- package/docs/zh/guides/topic-detail/framework-plugin/hook.mdx +1 -1
- package/docs/zh/guides/topic-detail/framework-plugin/implement.mdx +0 -1
- package/docs/zh/guides/topic-detail/framework-plugin/introduction.mdx +1 -1
- package/docs/zh/guides/topic-detail/framework-plugin/lifecycle.mdx +1 -1
- package/docs/zh/guides/topic-detail/framework-plugin/plugin-api.mdx +31 -11
- package/docs/zh/guides/topic-detail/framework-plugin/relationship.mdx +1 -1
- package/docs/zh/guides/topic-detail/generator/new/config.md +0 -5
- package/docs/zh/guides/topic-detail/micro-frontend/c03-main-app.mdx +2 -2
- package/docs/zh/guides/topic-detail/monorepo/create-sub-project.mdx +0 -14
- package/docs/zh/guides/topic-detail/monorepo/sub-project-interface.mdx +7 -43
- package/docs/zh/tutorials/first-app/c05-loader.mdx +2 -2
- package/docs/zh/tutorials/first-app/c06-model.mdx +3 -3
- package/docs/zh/tutorials/first-app/c07-container.mdx +3 -3
- package/docs/zh/tutorials/foundations/introduction.mdx +3 -2
- package/package.json +7 -7
@@ -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.
|
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
|
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.
|
61
|
+
import type { LoaderData } from './layout.data';
|
62
62
|
|
63
63
|
export default function Layout() {
|
64
64
|
const { data } = useLoaderData() as LoaderData;
|
@@ -31,7 +31,8 @@ We have prepared a tutorial on creating a "contact list app" that you can follow
|
|
31
31
|
|
32
32
|
We offer some commonly used case studies for your reference during the development process. Here you can find some usage patterns of the feature combinations provided by Modern.js. We will continue to improve the case studies here.
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
import ExampleList from '@site-docs-en/components/turtorials-example-list';
|
35
|
+
|
36
|
+
<ExampleList />
|
36
37
|
|
37
38
|
Let's start from [creating a project](tutorials/first-app/c01-start) now!
|
@@ -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
|
|
@@ -5,11 +5,11 @@ defineConfig(App, {
|
|
5
5
|
masterApp: {
|
6
6
|
apps: [{
|
7
7
|
name: 'Table',
|
8
|
-
entry: 'http://localhost:
|
8
|
+
entry: 'http://localhost:8081',
|
9
9
|
// activeWhen: '/table'
|
10
10
|
}, {
|
11
11
|
name: 'Dashboard',
|
12
|
-
entry: 'http://localhost:
|
12
|
+
entry: 'http://localhost:8082'
|
13
13
|
// activeWhen: '/dashboard'
|
14
14
|
}]
|
15
15
|
},
|
@@ -39,8 +39,8 @@ import { defineConfig } from '@modern-js/app-tools';
|
|
39
39
|
export default defineConfig({
|
40
40
|
source: {
|
41
41
|
entries: {
|
42
|
-
// 指定一个名称为
|
43
|
-
|
42
|
+
// 指定一个名称为 'my-entry' 的新入口
|
43
|
+
'my-entry': './src/home/test/index.ts',
|
44
44
|
},
|
45
45
|
// 禁用默认入口扫描
|
46
46
|
disableDefaultEntries: true,
|
@@ -92,9 +92,10 @@ import { defineConfig } from '@modern-js/app-tools';
|
|
92
92
|
export default defineConfig({
|
93
93
|
source: {
|
94
94
|
entries: {
|
95
|
-
|
95
|
+
'my-entry': {
|
96
96
|
// 入口文件路径
|
97
|
-
entry: './src/
|
97
|
+
entry: './src/my-page/index.tsx',
|
98
|
+
disableMount: true,
|
98
99
|
},
|
99
100
|
},
|
100
101
|
// 禁用默认入口扫描
|
@@ -113,8 +114,8 @@ export default defineConfig({
|
|
113
114
|
export default defineConfig({
|
114
115
|
source: {
|
115
116
|
entries: {
|
116
|
-
|
117
|
-
entry: './src/
|
117
|
+
'my-entry': {
|
118
|
+
entry: './src/my-page/index.tsx',
|
118
119
|
disableMount: true,
|
119
120
|
},
|
120
121
|
},
|
@@ -0,0 +1,119 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 4
|
3
|
+
title: 自定义请求 SDK
|
4
|
+
---
|
5
|
+
# 自定义请求 SDK
|
6
|
+
|
7
|
+
Modern.js 的 BFF 在 CSR 和 SSR 是同构的。在浏览器端依赖了[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch),在服务端依赖了 [node-fetch](https://www.npmjs.com/package/node-fetch)。但在很多业务场景下我们需要对请求或响应做一些额外的处理,例如:
|
8
|
+
|
9
|
+
- 在请求头中写入鉴权信息
|
10
|
+
- 对响应的数据或错误进行统一的处理
|
11
|
+
- 特定平台无法使用浏览器的原生 fetch 函数,需要使用其他方式发送请求
|
12
|
+
|
13
|
+
针对上述的场景,Modern.js 提供了 `configure` 函数,自定义能力从低到高,可以用它配置 ssr 透传请求头,拦截器,请求 SDK。
|
14
|
+
|
15
|
+
:::caution 注意
|
16
|
+
`configure` 函数的调用需要在所有 BFF 请求发送前调用,以确保覆盖默认的请求配置。
|
17
|
+
|
18
|
+
:::
|
19
|
+
|
20
|
+
|
21
|
+
```ts title="App.tsx"
|
22
|
+
import { configure } from '@modern-js/runtime/bff';
|
23
|
+
|
24
|
+
configure({
|
25
|
+
request: customRequest
|
26
|
+
})
|
27
|
+
```
|
28
|
+
|
29
|
+
## 配置 ssr 透传请求头
|
30
|
+
|
31
|
+
在同时使用 Modernjs SSR 和 BFF 的场景下,常常需要将 SSR 页面请求上的一些请求头信息,透传给 BFF 服务。
|
32
|
+
|
33
|
+
例如项目有页面地址是 `https://website.com`,该页面是 SSR 的,在组件中会调用 API 接口 `https://website.com/api/info`,该接口需要用户的 cookie 信息做鉴权。页面在请求该 API 接口时,需要将 SSR 页面请求的 `cookie` 传给 BFF。
|
34
|
+
|
35
|
+
目前以下请求头在 Modernjs 中是自动透传的:
|
36
|
+
|
37
|
+
- cookie
|
38
|
+
- x-tt-logid
|
39
|
+
- user-agent
|
40
|
+
- x-tt-stress
|
41
|
+
|
42
|
+
可以通过 `configure` 配置请求头。例如以下例子,Modern.js 会自动将 SSR 页面请求的 cookie 信息透传给 BFF 服务:
|
43
|
+
|
44
|
+
```tsx title="App.tsx"
|
45
|
+
import { configure } from '@modern-js/runtime/bff';
|
46
|
+
|
47
|
+
configure({
|
48
|
+
allowedHeaders: ['x-uid']
|
49
|
+
})
|
50
|
+
```
|
51
|
+
|
52
|
+
## 配置拦截器
|
53
|
+
|
54
|
+
在有些业务场景下需要对请求和响应进行一些统一的处理,这种场景下可以配置拦截器满足需求:
|
55
|
+
|
56
|
+
```tsx title="App.tsx"
|
57
|
+
configure({
|
58
|
+
// 这里的 request 是一体化默认的请求工具,interceptor 函数需返回一个新的 request。
|
59
|
+
// 新 request 的出参必须是 parse body 之后的结果
|
60
|
+
interceptor(request){
|
61
|
+
return async(url, params) => {
|
62
|
+
const res = await request(url, params);
|
63
|
+
return res.json();
|
64
|
+
};
|
65
|
+
}
|
66
|
+
});
|
67
|
+
```
|
68
|
+
|
69
|
+
## 配置自定义请求 SDK
|
70
|
+
|
71
|
+
如果仅仅通过配置拦截器无法满足需求,需要对请求的 SDK 做进一步的自定义,可以通过 `configure` 函数配置自定义请求 SDK:
|
72
|
+
|
73
|
+
:::caution 注意
|
74
|
+
在 SSR 和一体化调用的场景下,在 SSR 向 BFF 服务发送请求时,Modern.js 会通过**服务发现**找到 BFF 服务内网 IP,并通过 IP 发送请求,以提高性能。如果使用自定义请求 SDK 会**失去这种优化**。
|
75
|
+
|
76
|
+
:::
|
77
|
+
|
78
|
+
```tsx title="App.tsx"
|
79
|
+
import nodeFetch from 'node-fetch';
|
80
|
+
|
81
|
+
const customFetch = (input: RequestInfo | URL, init: RequestInit) => {
|
82
|
+
const curFetch = process.env.MODERN_TARGET !== 'node' ? fetch : nodeFetch as unknown as typeof fetch;
|
83
|
+
return curFetch(input, init).then(async res => {
|
84
|
+
const data = await res.json();
|
85
|
+
data.hello = 'hello custom sdk';
|
86
|
+
return data;
|
87
|
+
});
|
88
|
+
};
|
89
|
+
|
90
|
+
configure({
|
91
|
+
request: customFetch,
|
92
|
+
});
|
93
|
+
```
|
94
|
+
|
95
|
+
配置自定义请求 SDK 有以下约定:
|
96
|
+
|
97
|
+
- 通过 `configure` 函数可以配置一个 `request` 函数,这个函数的入参与浏览器中的 Fetch 或 node-fetch 对齐,所有的一体化 BFF 函数会通过该函数发送请求。
|
98
|
+
- `request` 函数出参必须是接口实际返回的数据,不能是 Promise,否则会导致一体化 BFF 函数无法正常返回数据。
|
99
|
+
- 如果是 SSR 项目,`request` 必须要同时支持浏览器端和服务器端发送请求。
|
100
|
+
|
101
|
+
使用 axios 定制自定义请求 SDK 的示例:
|
102
|
+
|
103
|
+
```tsx title="App.tsx"
|
104
|
+
import { configure } from '@modern-js/runtime/bff';
|
105
|
+
import type { Method, AxiosRequestHeaders as Headers } from 'axios';
|
106
|
+
|
107
|
+
configure({
|
108
|
+
async request(...config: Parameters<typeof fetch>) {
|
109
|
+
const [url, params] = config;
|
110
|
+
const res = await axios({
|
111
|
+
url: url as string, // 这里因为 fetch 和 axios 类型有些不兼容,需要使用 as
|
112
|
+
method: params?.method as Method,
|
113
|
+
data: params?.body,
|
114
|
+
headers: params?.headers as Headers,
|
115
|
+
});
|
116
|
+
return res.data;
|
117
|
+
},
|
118
|
+
});
|
119
|
+
```
|
@@ -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.
|
26
|
-
export
|
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.
|
151
|
-
export
|
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.
|
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.
|
310
|
+
```ts title="routes/page.data.tsx"
|
311
311
|
import fse from 'fs-extra';
|
312
|
-
export
|
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.
|
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
|
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.
|
385
|
+
```ts title="page.data.ts"
|
386
386
|
// 省略部分代码
|
387
387
|
|
388
|
-
export
|
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.
|
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.
|
455
|
+
所以,这里的导入方式为:`import type { Data } from './page.data'`;
|
456
456
|
|
457
457
|
:::
|
458
458
|
|
@@ -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`)可以有一个同名的 `
|
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`,可以定义同名的 `
|
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.
|
37
|
+
├── layout.data.ts
|
32
38
|
├── page.tsx
|
33
|
-
└── page.
|
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.
|
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.
|
54
|
+
```ts title="routes/user/page.data.ts"
|
49
55
|
export type ProfileData = {
|
50
56
|
/* some types */
|
51
57
|
};
|
52
58
|
|
53
|
-
export
|
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
|
-
这里路由组件和 `
|
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.
|
90
|
+
// routes/user/[id]/page.data.ts
|
85
91
|
import { LoaderFunctionArgs } from '@modern-js/runtime/router';
|
86
92
|
|
87
|
-
export
|
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.
|
109
|
+
// routes/user/[id]/page.data.ts
|
104
110
|
import { LoaderFunctionArgs } from '@modern-js/runtime/router';
|
105
111
|
|
106
|
-
export
|
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.
|
156
|
-
export
|
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
|
188
|
-
// 获取 routes/user/layout.
|
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.
|
229
|
+
创建 `user/layout.data.ts`,并添加以下代码:
|
224
230
|
|
225
|
-
```ts title="routes/user/layout.
|
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
|
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.
|
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.
|
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.
|
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
|
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
|
};
|