@modern-js/main-doc 2.0.0-beta.3 → 2.0.0-beta.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/dev.md +8 -3
  3. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/inspect.md +31 -10
  4. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/serve.md +32 -0
  5. package/en/docusaurus-plugin-content-docs/current/apis/app/hooks/src/server.md +31 -0
  6. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/bootstrap.md +4 -3
  7. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/create-app.md +2 -3
  8. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/use-module-apps.md +1 -1
  9. package/en/docusaurus-plugin-content-docs/current/components/init-app.md +42 -0
  10. package/en/docusaurus-plugin-content-docs/current/configure/app/builder-plugins.md +70 -0
  11. package/en/docusaurus-plugin-content-docs/current/configure/app/dev/with-master-app.md +0 -1
  12. package/en/docusaurus-plugin-content-docs/current/configure/app/plugins.md +11 -5
  13. package/en/docusaurus-plugin-content-docs/current/configure/app/server/routes.md +2 -4
  14. package/en/docusaurus-plugin-content-docs/current/configure/app/source/disable-entry-dirs.md +38 -0
  15. package/en/docusaurus-plugin-content-docs/current/configure/app/source/entries.md +66 -2
  16. package/en/docusaurus-plugin-content-docs/current/configure/app/tools/esbuild.md +16 -39
  17. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/css-in-js.md +38 -0
  18. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/css-modules.md +86 -0
  19. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/less-sass.md +17 -0
  20. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/postcss.md +81 -0
  21. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/tailwindcss.md +95 -0
  22. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/data-fetch.md +66 -0
  23. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/routes.md +270 -0
  24. package/en/docusaurus-plugin-content-docs/current/guides/concept/entries.md +116 -0
  25. package/en/docusaurus-plugin-content-docs/current/guides/get-started/quick-start.md +162 -0
  26. package/en/docusaurus-plugin-content-docs/current/guides/get-started/upgrade.md +78 -0
  27. package/{zh/tutorials/first-app → en/docusaurus-plugin-content-docs/current/guides}/overview.md +4 -4
  28. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/_category_.json +4 -0
  29. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/lifecycle.md +15 -0
  30. package/en/docusaurus-plugin-content-docs/current/tutorials/foundations/introduction.md +1 -1
  31. package/en/docusaurus-plugin-content-docs/current.json +11 -11
  32. package/package.json +4 -4
  33. package/zh/apis/app/commands/dev.md +9 -4
  34. package/zh/apis/app/commands/inspect.md +32 -11
  35. package/zh/apis/app/commands/new.md +1 -1
  36. package/zh/apis/app/commands/{start.md → serve.md} +3 -3
  37. package/zh/apis/app/hooks/src/index_.md +5 -4
  38. package/zh/apis/app/hooks/src/server.md +31 -0
  39. package/zh/apis/app/runtime/core/bootstrap.md +3 -4
  40. package/zh/apis/app/runtime/core/create-app.md +1 -18
  41. package/zh/apis/app/runtime/core/use-module-apps.md +64 -33
  42. package/zh/apis/app/runtime/web-server/hook.md +1 -1
  43. package/zh/apis/app/runtime/web-server/middleware.md +1 -0
  44. package/zh/components/debug-app.md +18 -0
  45. package/zh/components/default-mwa-generate.md +5 -0
  46. package/zh/components/deploy.md +1 -0
  47. package/zh/components/enable-micro-frontend.md +13 -0
  48. package/zh/components/global-proxy.md +28 -0
  49. package/zh/components/init-app.md +44 -0
  50. package/zh/components/micro-runtime-config.md +18 -0
  51. package/zh/components/prerequisites.md +19 -0
  52. package/zh/components/release-note.md +1 -0
  53. package/zh/configure/app/builder-plugins.md +72 -0
  54. package/zh/configure/app/deploy/_category_.json +4 -0
  55. package/zh/configure/app/deploy/microFrontend.md +64 -0
  56. package/zh/configure/app/dev/with-master-app.md +0 -2
  57. package/zh/configure/app/plugins.md +10 -4
  58. package/zh/configure/app/runtime/master-app.md +33 -36
  59. package/zh/configure/app/server/routes.md +2 -4
  60. package/zh/configure/app/source/disable-entry-dirs.md +37 -0
  61. package/zh/configure/app/source/entries-dir.md +0 -3
  62. package/zh/configure/app/source/entries.md +66 -3
  63. package/zh/configure/app/tools/esbuild.md +16 -39
  64. package/zh/guides/advanced-features/bff/bff-proxy.md +1 -1
  65. package/zh/guides/advanced-features/compatibility.md +14 -39
  66. package/zh/guides/advanced-features/eslint.md +21 -21
  67. package/zh/guides/advanced-features/ssg.md +20 -9
  68. package/zh/guides/advanced-features/ssr.md +95 -52
  69. package/zh/guides/advanced-features/testing.md +44 -1
  70. package/zh/guides/advanced-features/web-server.md +14 -3
  71. package/zh/guides/basic-features/css/tailwindcss.md +13 -6
  72. package/zh/guides/basic-features/data-fetch.md +398 -5
  73. package/zh/guides/basic-features/html.md +182 -0
  74. package/zh/guides/basic-features/mock.md +3 -9
  75. package/zh/guides/basic-features/proxy.md +2 -27
  76. package/zh/guides/basic-features/routes.md +35 -3
  77. package/zh/guides/concept/entries.md +108 -19
  78. package/zh/guides/get-started/quick-start.md +14 -83
  79. package/zh/guides/get-started/upgrade.md +11 -9
  80. package/zh/guides/{concept → topic-detail/framework-plugin}/lifecycle.md +0 -0
  81. package/zh/guides/topic-detail/micro-frontend/c01-introduction.md +29 -0
  82. package/zh/guides/topic-detail/micro-frontend/c02-development.md +191 -0
  83. package/zh/guides/topic-detail/micro-frontend/c03-main-app.md +246 -0
  84. package/zh/guides/topic-detail/micro-frontend/c04-communicate.md +54 -0
  85. package/zh/guides/topic-detail/micro-frontend/{mixed-stack.md → c05-mixed-stack.md} +3 -3
  86. package/zh/guides/topic-detail/model/quick-start.md +1 -1
  87. package/zh/guides/topic-detail/model/test-model.md +2 -2
  88. package/zh/guides/topic-detail/monorepo/create-sub-project.md +2 -2
  89. package/zh/guides/topic-detail/monorepo/intro.md +1 -1
  90. package/zh/guides/troubleshooting/dependencies.md +0 -69
  91. package/zh/tutorials/first-app/_category_.json +1 -1
  92. package/zh/tutorials/first-app/c01-start.md +99 -0
  93. package/zh/tutorials/first-app/{c05-component/5.1-use-ui-library.md → c02-component.md} +13 -15
  94. package/zh/tutorials/first-app/c03-css.md +324 -0
  95. package/zh/tutorials/first-app/{c08-client-side-routing/8.1-code-based-routing.md → c04-routes.md} +52 -39
  96. package/zh/tutorials/first-app/c05-loader.md +82 -0
  97. package/zh/tutorials/first-app/c06-model.md +256 -0
  98. package/zh/tutorials/first-app/c07-container.md +283 -0
  99. package/zh/tutorials/first-app/c08-entries.md +137 -0
  100. package/zh/tutorials/foundations/introduction.md +1 -1
  101. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/start.md +0 -32
  102. package/en/docusaurus-plugin-content-docs/current/configure/app/output/enable-modern-mode.md +0 -34
  103. package/zh/apis/generator/overview.md +0 -32
  104. package/zh/configure/app/output/enable-modern-mode.md +0 -34
  105. package/zh/guides/advanced-features/custom-app.md +0 -72
  106. package/zh/guides/topic-detail/micro-frontend/communicate.md +0 -39
  107. package/zh/guides/topic-detail/micro-frontend/debugging.md +0 -168
  108. package/zh/guides/topic-detail/micro-frontend/introduction.md +0 -13
  109. package/zh/guides/topic-detail/micro-frontend/route-mode.md +0 -110
  110. package/zh/guides/topic-detail/monorepo/deploy.md +0 -43
  111. package/zh/tutorials/first-app/c01-getting-started/1.1-prerequisites.md +0 -25
  112. package/zh/tutorials/first-app/c01-getting-started/1.2-minimal-mwa.md +0 -118
  113. package/zh/tutorials/first-app/c01-getting-started/1.3-dev-command.md +0 -29
  114. package/zh/tutorials/first-app/c01-getting-started/1.4-enable-ssr.md +0 -47
  115. package/zh/tutorials/first-app/c01-getting-started/1.5-start-command.md +0 -18
  116. package/zh/tutorials/first-app/c01-getting-started/1.6-create-repo.md +0 -31
  117. package/zh/tutorials/first-app/c01-getting-started/_category_.json +0 -3
  118. package/zh/tutorials/first-app/c02-generator-and-studio/2.1-generator.md +0 -79
  119. package/zh/tutorials/first-app/c02-generator-and-studio/2.2-boilerplates.md +0 -34
  120. package/zh/tutorials/first-app/c02-generator-and-studio/2.3-configuration.md +0 -19
  121. package/zh/tutorials/first-app/c02-generator-and-studio/_category_.json +0 -3
  122. package/zh/tutorials/first-app/c03-ide/3.1-setting-up.md +0 -55
  123. package/zh/tutorials/first-app/c03-ide/3.2-hints-in-ide.md +0 -60
  124. package/zh/tutorials/first-app/c03-ide/3.3-autofix-in-ide.md +0 -11
  125. package/zh/tutorials/first-app/c03-ide/3.4-autofix-in-cli.md +0 -63
  126. package/zh/tutorials/first-app/c03-ide/_category_.json +0 -3
  127. package/zh/tutorials/first-app/c04-es6-plus-and-ts/4.1-use-es6-plus.md +0 -54
  128. package/zh/tutorials/first-app/c04-es6-plus-and-ts/4.2-use-typescript.md +0 -135
  129. package/zh/tutorials/first-app/c04-es6-plus-and-ts/4.3-compatibility.md +0 -67
  130. package/zh/tutorials/first-app/c04-es6-plus-and-ts/_category_.json +0 -3
  131. package/zh/tutorials/first-app/c05-component/5.2-use-standalone-component.md +0 -72
  132. package/zh/tutorials/first-app/c05-component/_category_.json +0 -3
  133. package/zh/tutorials/first-app/c06-css-and-component/6.1-css-in-js.md +0 -110
  134. package/zh/tutorials/first-app/c06-css-and-component/6.2-utility-class.md +0 -143
  135. package/zh/tutorials/first-app/c06-css-and-component/6.3-postcss.md +0 -84
  136. package/zh/tutorials/first-app/c06-css-and-component/6.4-design-system.md +0 -83
  137. package/zh/tutorials/first-app/c06-css-and-component/6.5-storybook.md +0 -77
  138. package/zh/tutorials/first-app/c06-css-and-component/6.6-testing.md +0 -104
  139. package/zh/tutorials/first-app/c06-css-and-component/_category_.json +0 -3
  140. package/zh/tutorials/first-app/c07-app-entry/7.1-intro.md +0 -69
  141. package/zh/tutorials/first-app/c07-app-entry/7.2-add-entry-in-cli.md +0 -100
  142. package/zh/tutorials/first-app/c07-app-entry/7.3-manage-entries-by-hand.md +0 -69
  143. package/zh/tutorials/first-app/c07-app-entry/_category_.json +0 -3
  144. package/zh/tutorials/first-app/c08-client-side-routing/_category_.json +0 -3
  145. package/zh/tutorials/first-app/c09-bff/9.1-serverless.md +0 -30
  146. package/zh/tutorials/first-app/c09-bff/9.2-enable-bff.md +0 -95
  147. package/zh/tutorials/first-app/c09-bff/9.3-fetch-bff.md +0 -131
  148. package/zh/tutorials/first-app/c09-bff/_category_.json +0 -3
  149. package/zh/tutorials/first-app/c10-model/10.1-application-architecture.md +0 -21
  150. package/zh/tutorials/first-app/c10-model/10.2-add-model.md +0 -185
  151. package/zh/tutorials/first-app/c10-model/10.3-use-model.md +0 -55
  152. package/zh/tutorials/first-app/c10-model/10.4-testing.md +0 -69
  153. package/zh/tutorials/first-app/c10-model/_category_.json +0 -3
  154. package/zh/tutorials/first-app/c11-container/11.1-use-model-with-app-state.md +0 -240
  155. package/zh/tutorials/first-app/c11-container/11.2-add-container.md +0 -109
  156. package/zh/tutorials/first-app/c11-container/11.3-use-loader.md +0 -63
  157. package/zh/tutorials/first-app/c11-container/11.4-testing.md +0 -56
  158. package/zh/tutorials/first-app/c11-container/_category_.json +0 -3
@@ -1,131 +0,0 @@
1
- ---
2
- title: 从 BFF 获取数据
3
- ---
4
-
5
- 上一小节中,我们创建了【 函数写法 】的 BFF,这一小节中,我们将用该模式为联系人列表实现一个数据接口。
6
-
7
- 我们首先把不需要的样板文件清理掉,把 `index.ts` 重命名为 `contacts.ts`,执行以下命令:
8
-
9
- import Tabs from '@theme/Tabs';
10
- import TabItem from '@theme/TabItem';
11
-
12
- <Tabs>
13
- <TabItem value="macOS" label="macOS" default>
14
-
15
- ```bash
16
- rm -r api/info api/_app.ts
17
- mv api/index.ts api/contacts.ts
18
- ```
19
-
20
- </TabItem>
21
- <TabItem value="Windows" label="Windows">
22
-
23
- ```powershell
24
- rm -r api/info
25
- rm api/_app.ts
26
- mv api/index.ts api/contacts.ts
27
- ```
28
-
29
- </TabItem>
30
- </Tabs>
31
-
32
-
33
- 此时 API 路由将变为 `/api/contacts`。
34
-
35
- 我们使用 [faker](https://github.com/Marak/Faker.js) 来 mock 需要的数据,首先安装依赖:
36
-
37
- ```bash
38
- pnpm add faker@5.5.3
39
- ```
40
-
41
- 将 `api/contacts.ts` 内容改为:
42
-
43
- ```js
44
- import { name, internet } from 'faker';
45
-
46
- export const get = async () => {
47
- const mockData = new Array(20).fill(0).map(() => {
48
- const firstName = name.firstName();
49
- return {
50
- name: firstName,
51
- avatar: `https://avatars.dicebear.com/api/identicon/${firstName}.svg`,
52
- email: internet.email(),
53
- };
54
- });
55
-
56
- return { code: 200, data: mockData };
57
- };
58
- ```
59
-
60
- :::info 注
61
- 数据同样可以从远端接口获取,此处仅为了演示。
62
- :::
63
-
64
- 执行 `pnpm run dev`,访问 `http://localhost:8080/api/contacts`:
65
-
66
- ![browser-result](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/api-result.png)
67
-
68
- 接下来我们把 `src/routes/contacts/page.tsx` 里硬编码的 `getMockData` 改成从 BFF 加载:
69
-
70
- ```tsx
71
- import { Helmet } from '@modern-js/runtime/head';
72
- import { List } from 'antd';
73
- import 'ladda/dist/ladda.min.css';
74
- import 'tailwindcss/base.css';
75
- import 'tailwindcss/components.css';
76
- import 'tailwindcss/utilities.css';
77
- import './styles/utils.css';
78
-
79
- import { useState, useEffect } from 'react';
80
- import { get as contacts } from '@api/contacts';
81
- import Item from './components/Item';
82
-
83
- function Index() {
84
- const [list, setList] = useState(
85
- [] as Array<{ name: string; email: string; avatar: string }>,
86
- );
87
- const loadMockData = async () => {
88
- const { data } = await contacts();
89
- setList(data);
90
- };
91
- useEffect(() => {
92
- if (!list.length) {
93
- loadMockData();
94
- }
95
- });
96
- return (
97
- <div className="container lg mx-auto">
98
- <Helmet>
99
- <title>All</title>
100
- </Helmet>
101
- {(list.length && (
102
- <List
103
- dataSource={list}
104
- renderItem={info => <Item key={info.name} info={info} />}
105
- />
106
- )) || (
107
- <div className="p-4 items-center border-gray-200 border-b border-t custom-text-gray">
108
- Pending...
109
- </div>
110
- )}
111
- </div>
112
- );
113
- }
114
-
115
- export default Index;
116
- ```
117
-
118
- 在 Modern.js 中,可以通过调用函数(前后端一体化)的方式,直接调用 BFF 接口,调用时无需关心域名、路径等。
119
-
120
- :::info 注
121
- 在使用 `pnpm run new` 创建 BFF 时,Modern.js 已经默认在 `tsconig.json` 中注入了别名。这也是之前提到的,生成器在项目创建之后并不会被抛弃,仍旧可以在开发过程中不断为项目提供新的内容。
122
- :::
123
-
124
- 执行 `pnpm run dev`,再打开页面`http://localhost:8000/contacts`,可以看到页面加载后,会先初始化联系人数据(显示 pending),之后每次切换到 All 列表,也会重新请求联系人数据:
125
-
126
- ![browser-result](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/08/browser-result.png)
127
-
128
- ---
129
-
130
- > 本小节的代码可以在[这里查看](https://github.com/modern-js-dev/modern-js-examples/tree/main/tutorials/c09/hello-modern-3)。
131
-
@@ -1,3 +0,0 @@
1
- {
2
- "label": "9: 添加 BFF"
3
- }
@@ -1,21 +0,0 @@
1
- ---
2
- title: 应用架构
3
- ---
4
-
5
- 上一章节中,我们把硬编码的 `mockData` 改成从 BFF 加载,在 `pages.tsx` 组件里用 BFF 函数,获取到联系人数据之后,保存在 `Index` 的组件内部状态里,而 `archives/index` 组件暂时继续使用 mock 数据。
6
-
7
- 本章节中,为了进一步实现项目功能,我们需要让两个组件都从 BFF 获取数据,还要实现【 Archive 】按钮,点击按钮能把联系人归档。
8
-
9
- 业务逻辑变复杂之后,相关代码不可避免会变多,如果都写在组件里,都会让这个组件的可读性和可维护性变差,让做不同事情的代码混在一起。
10
-
11
- 原本可以跟 UI 完全解耦的业务逻辑(比如网络请求、纯数据的操作、状态的管理等)跟 UI 逻辑(比如联系人列表如何展现)混在一起,当需要修改 UI 的时候,不得不跟 UI 无关的业务逻辑代码打交道,反过来也一样,不符合【 [单一职责原则(SRP)](https://zh.wikipedia.org/wiki/%E5%8D%95%E4%B8%80%E5%8A%9F%E8%83%BD%E5%8E%9F%E5%88%99)】,也做不到【 [关注点分离(SoC)](https://zh.wikipedia.org/wiki/%E5%85%B3%E6%B3%A8%E7%82%B9%E5%88%86%E7%A6%BB)】。
12
-
13
- 从这里开始,我们发现如果这是个真实的项目,继续这样写下去会产生越来越多面条式代码,仅凭 React 组件这一种抽象方式,不足以让代码足够结构化,随着项目不断积累业务逻辑、复杂性和变更,技术债也会积累,最终可能变成没人愿意碰的祖传代码。
14
-
15
- 像这样的项目,需要更健全的**客户端应用架构**。
16
-
17
- Modern.js 提供开箱即用的**应用架构**,支持几种扮演不同**角色(role)**、属于不同**分层(layer)**的代码模块类型,可以把业务逻辑解耦到不同类型的代码模块里,做到高[内聚](https://zh.wikipedia.org/wiki/%E5%85%A7%E8%81%9A%E6%80%A7_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8))低[耦合](https://zh.wikipedia.org/wiki/%E8%80%A6%E5%90%88%E6%80%A7_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8))。
18
-
19
- 当前项目里 `components/` 目录中的 React 组件,是一种可以称作【 视图组件 】的代码模块,负责 UI、交互上的界面展现。
20
-
21
- 本章节我们来把 `page.tsx` 中的 Index 组件中可以跟 UI 解耦的业务逻辑,移到一种叫【 Model(业务模型)】的代码模块里。
@@ -1,185 +0,0 @@
1
- ---
2
- title: 实现 Model
3
- ---
4
-
5
- 创建一个完整的 Model 首先需要定义**状态(state)**,包括状态中数据的名称和初始值。
6
-
7
- 我们使用 Model 来管理联系人列表的数据,因此定义如下数据状态:
8
-
9
- ```js
10
- const state = {
11
- items: [],
12
- };
13
- ```
14
-
15
- 使用 TS 语法,可以定义更完整的类型信息,比如 items 里每个对象都应该有 `name`、`email` 字段;
16
-
17
- 为了实现归档功能,还需要创建 `archived` 字段保存这个联系人是否已被归档的状态;
18
-
19
- 我们还需要一个字段用来访问所有已归档的联系人,可以定义 **computed** 类型的字段,对已有的数据做转换:
20
-
21
- ```js
22
- const computed = {
23
- archived: ({ items }) => {
24
- return items.filter(item => item.archived);
25
- },
26
- };
27
- ```
28
-
29
- :::info 注
30
- 当前版本还未支持 computed,本章节后续部分会先使用其他方式实现 archived 列表,这里只做介绍。
31
- :::
32
-
33
- computed 类型字段的定义方式是函数,但使用时可以像普通字段一样通过 state 访问。
34
-
35
- Modern.js 支持的 Model 模块跟 React 组件一样,是基于 FP(Functional Programming)而不是 OOP(Object-Oriented Programming)的,对状态的管理是基于**不可变数据**的,不会修改状态中的数据,只会从一个状态转移到另一个状态,这样的好处很多,比如保障程序的可靠性、方便调试、方便记录和还原状态等。
36
-
37
- 由于 JS 没有原生支持不可变数据,为了提高编写这种代码的效率,Modern.js 集成了 [Immer](https://immerjs.github.io/immer/),能够像操作 JS 中常规的可变数据一样,去写这种状态转移的逻辑。
38
-
39
- 实现 Archive 按钮时,我们需要一个 `archive` 函数,负责修改指定联系人的 `archived` 字段,我们把这种函数都叫作 **action**:
40
-
41
- ```js
42
- const actions = {
43
- archive(draft, payload) {
44
- const target = draft.items.find(item => item.email === payload);
45
- if (target) {
46
- target.archived = true;
47
- }
48
- },
49
- };
50
- ```
51
-
52
- action 函数是一种**纯函数**,确定的输入得到确定的输出(转移后的状态),不应该有任何副作用。
53
-
54
- 函数的第一个参数是 Immer 提供的 Draft State,第二个参数是 action 被调用时传入的参数(后面会介绍怎么调用)。
55
-
56
- Model 里也可以定义 Side Effect,比如我们需要从 BFF 加载这个联系人列表的数据,这段业务逻辑可以写成:
57
-
58
- ```js
59
- const effects = {
60
- async load() {
61
- const { data } = await contacts();
62
- return data;
63
- },
64
- };
65
- ```
66
-
67
- 一个 Side Effect 有多种实现方式,上面使用的是 Async 函数方式,这种方式是最简单直观的。Modern.js 会根据它返回的 Promise 对象的状态变化,自动触发不同的 action。
68
-
69
- 因此一个 effect 总共有三个 action,命名里会用 Side Effect 的名称作为命名空间,在这个例子里,分别是:
70
-
71
- - `load.pending`:等待中
72
- - `load.fulfilled`:成功,得到结果
73
- - `load.rejected`:失败,得到错误信息
74
-
75
- Modern.js 虽然会自动定义和触发这些 action,但默认不会为这些 action 实现具体的业务逻辑(action 直接返回原本的状态,不做任何转换)。
76
-
77
- 我们尝试自己实现它们:
78
-
79
- ```js
80
- import _ from 'lodash';
81
-
82
- const state = {
83
- items: [],
84
- pending: false,
85
- error: null,
86
- };
87
-
88
- const computed = {
89
- archived: ({ items }) => {
90
- return items.filter(item => item.archived);
91
- },
92
- };
93
-
94
- const actions = {
95
- archive(draft, payload) {
96
- const target = draft.items.find(item => item.email === payload);
97
- if (target) {
98
- target.archived = true;
99
- }
100
- },
101
- load: {
102
- pending(draft) {
103
- draft.pending = true;
104
- },
105
- fulfilled(draft, payload) {
106
- draft.pending = false;
107
- _.merge(draft.items, payload);
108
- },
109
- rejected(draft, payload) {
110
- draft.pending = false;
111
- draft.error = payload;
112
- },
113
- },
114
- };
115
- ```
116
-
117
- 上述实现里,成功时,payload 是 promise 的结果;失败时,payload 是错误信息。
118
-
119
- 从上面这个例子里可以看到,可以用嵌套的写法,实现 `load.pending` 这样名称中包含命名空间的 action。
120
-
121
- 为了做到高内聚低耦合,一个 Model 的 state、action、side effect 不应该分散在不同文件里。接下来我们把上面的代码连起来,放在同一个 Model 文件里,执行以下命令:
122
-
123
- ```bash
124
- mkdir -p src/contacts/routes/models/
125
- touch src/contacts/routes/models/contacts.ts
126
- ```
127
-
128
- `src/contacts/routes/models/contacts.ts` 的内容:
129
-
130
- ```tsx
131
- import { model } from '@modern-js/runtime/model';
132
- import { get as contacts } from '@api/contacts';
133
-
134
- type State = {
135
- items: {
136
- avatar: string;
137
- name: string;
138
- email: string;
139
- archived?: boolean;
140
- }[];
141
- pending: boolean;
142
- error: null | Error;
143
- };
144
-
145
- export default model<State>('contacts').define({
146
- state: {
147
- items: [],
148
- pending: false,
149
- error: null,
150
- },
151
- computed: {
152
- archived: ({ items }: State) => items.filter(item => item.archived),
153
- },
154
- actions: {
155
- archive(draft, payload) {
156
- const target = draft.items.find(item => item.email === payload)!;
157
- if (target) {
158
- target.archived = true;
159
- }
160
- },
161
- load: {
162
- pending(draft) {
163
- draft.pending = true;
164
- },
165
- rejected(draft, payload) {
166
- draft.pending = false;
167
- draft.error = payload;
168
- },
169
- fulfilled(draft, p) {
170
- draft.items = p;
171
- },
172
- },
173
- },
174
- effects: {
175
- async load() {
176
- const { data } = await contacts();
177
- return data;
178
- },
179
- },
180
- });
181
- ```
182
-
183
- 我们把一个包含 state,action 等要素的 plain object 称作【 Model Spec 】,Modern.js 提供了 [Model API](/docs/apis/app/runtime/model/model_),可以根据 Model Spec 生成【 Model 】。
184
-
185
- 本小节中,我们联系人列表项目需要的 Model 实现。下一小节我们将会学习如何使用 Model。
@@ -1,55 +0,0 @@
1
- ---
2
- title: 使用 Model
3
- ---
4
-
5
- 上一小节中,我们实现了 Model,已经把 `page.tsx` `Index` 组件中原有的 UI 无关业务逻辑解耦出来。
6
-
7
- 这一小节中,我们使用这个 Model 直接把 `page.tsx` `Index` 组件重新还原出来,实现变得更简洁清晰:
8
-
9
- ```js title="src/contacts/routes/page.tsx"
10
- import { useEffect } from 'react';
11
- import { useLocalModel } from '@modern-js/runtime/model';
12
- import contacts from './models/contacts';
13
- import Item from './components/Item';
14
-
15
- function Index() {
16
- const [{ items, error, pending }, actions] = useLocalModel(contacts);
17
-
18
- useEffect(() => {
19
- if (!items.length && !error && !pending) {
20
- actions.load();
21
- }
22
- });
23
- return (
24
- <div className="container lg mx-auto">
25
- {(items.length && (
26
- <List
27
- dataSource={items}
28
- renderItem={info => <Item key={info.name} info={info} />}
29
- />
30
- )) || (
31
- <div className="p-4 items-center border-gray-200 border-b border-t custom-text-gray">
32
- Pending...
33
- </div>
34
- )}
35
- </div>
36
- );
37
- }
38
-
39
- export default Index;
40
- ```
41
-
42
- 以上代码跟上一章节中 `Index` 组件的代码等价。
43
-
44
- `useLocalModel` 是 Modern.js 提供的 hooks API,可以使用 Model,在组件中提供 Model 中定义的 state,或通过 actions 调用 Model 中定义的 side effect 与 action,从而改变 Model 的 state。
45
-
46
- Modern.js 的 Model 能够这样使用的原因是 Model 是基于 FP 的,不像 OOP 的 Model 那样自身持有和封装了状态,对外部提供方法访问和修改自身内部的状态。
47
-
48
- Model 是业务逻辑,是计算过程,本身不创建也不持有状态,只有在被组件用 hooks API 使用后,才在指定的地方(比如这个例子里,是组件内部的 state)创建状态。
49
-
50
- 执行 `pnpm run dev`,可以看到跟上一章节一样的效果。
51
-
52
- ---
53
-
54
- > 本小节的代码可以在[这里查看](https://github.com/modern-js-dev/modern-js-examples/tree/main/tutorials/c10/hello-modern-3)。
55
-
@@ -1,69 +0,0 @@
1
- ---
2
- title: 测试 Model
3
- ---
4
-
5
- 上一小节中我们学习了如何使用 Model。
6
-
7
- 这一小节里我们来测试 Model。
8
-
9
- 跟[测试组件​​​](../c06-css-and-component/6.6-testing.md)中一样,不需要做任何配置,可以直接给 Model 写测试用例。
10
-
11
- 以 `models/contacts` 为例,我们创建对应的 `.test` 文件:
12
-
13
- import Tabs from '@theme/Tabs';
14
- import TabItem from '@theme/TabItem';
15
-
16
- <Tabs>
17
- <TabItem value="macOS" label="macOS" default>
18
-
19
- ```bash
20
- touch src/contacts/routes/models/contacts.test.ts
21
- ```
22
-
23
- </TabItem>
24
- <TabItem value="Windows" label="Windows">
25
-
26
- ```powershell
27
- ni src/contacts/routes/models/contacts.test.ts
28
- ```
29
-
30
- </TabItem>
31
- </Tabs>
32
-
33
- 在测试用例中可以使用 Modern.js 提供的 Model 模拟,可以调用这个对象的方法写断言。
34
-
35
- 我们直接使用 createStore API 来编写测试文件,示例内容如下:
36
-
37
- ```ts
38
- import { createStore } from '@modern-js/runtime/testing';
39
- import contactsModel from './contacts';
40
-
41
- const mockItem = {
42
- avatar: '',
43
- name: '李华',
44
- age: '16',
45
- email: 'lihua@gmail.com',
46
- archived: false,
47
- };
48
-
49
- describe('test contracts model', () => {
50
- it('actions works well', async () => {
51
- const store = createStore();
52
- const [state, actions] = store.use(contactsModel);
53
-
54
- state.items.push(mockItem);
55
- expect(store.use(contactsModel)[0].items.length).toBe(1);
56
- expect(store.use(contactsModel)[0].items[0].archived).toBeFalsy();
57
-
58
- await actions.archive('lihua@gmail.com');
59
- expect(store.use(contactsModel)[0].items[0].archived).toBeTruthy();
60
- });
61
- });
62
- ```
63
-
64
- 执行 `pnpm run test`,可以看到测试报告。
65
-
66
- ---
67
-
68
- > 本小节的代码可以在[这里查看](https://github.com/modern-js-dev/modern-js-examples/tree/main/tutorials/c10/hello-modern-4)。
69
-
@@ -1,3 +0,0 @@
1
- {
2
- "label": "10: 添加业务模型"
3
- }