@modern-js/main-doc 2.0.0-canary.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/_category_.json +1 -1
  3. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/use-module-apps.md +62 -31
  4. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/router/router.md +174 -375
  5. package/en/docusaurus-plugin-content-docs/current/components/enable-micro-frontend.md +13 -0
  6. package/en/docusaurus-plugin-content-docs/current/components/micro-master-manifest-config.md +15 -0
  7. package/en/docusaurus-plugin-content-docs/current/components/micro-runtime-config.md +18 -0
  8. package/en/docusaurus-plugin-content-docs/current/components/router-legacy-tip.md +1 -0
  9. package/en/docusaurus-plugin-content-docs/current/configure/app/auto-load-plugin.md +62 -0
  10. package/en/docusaurus-plugin-content-docs/current/configure/app/deploy/microFrontend.md +54 -0
  11. package/en/docusaurus-plugin-content-docs/current/configure/app/output/ssg.md +226 -0
  12. package/en/docusaurus-plugin-content-docs/current/configure/app/runtime/master-app.md +20 -39
  13. package/en/docusaurus-plugin-content-docs/current/configure/app/runtime/router.md +17 -4
  14. package/en/docusaurus-plugin-content-docs/current/configure/app/runtime/state.md +17 -4
  15. package/en/docusaurus-plugin-content-docs/current/configure/app/server/enable-framework-ext.md +47 -0
  16. package/en/docusaurus-plugin-content-docs/current/guides/advanced-features/bff/function.md +2 -2
  17. package/en/docusaurus-plugin-content-docs/current/guides/advanced-features/ssg.md +6 -2
  18. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/_category_.json +4 -0
  19. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/data-fetch.md +1 -1
  20. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/routes.md +0 -2
  21. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/_category_.json +5 -0
  22. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/extend.md +162 -0
  23. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/hook-list.md +803 -0
  24. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/hook.md +169 -0
  25. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/implement.md +247 -0
  26. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/introduction.md +49 -0
  27. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/plugin-api.md +116 -0
  28. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/relationship.md +118 -0
  29. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/generator/config/common.md +1 -1
  30. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/generator/config/module.md +3 -1
  31. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/generator/config/mwa.md +1 -9
  32. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/generator/project.md +2 -2
  33. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/micro-frontend/_category_.json +4 -0
  34. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/micro-frontend/c01-introduction.md +29 -0
  35. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/micro-frontend/c02-development.md +191 -0
  36. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/micro-frontend/c03-main-app.md +246 -0
  37. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/micro-frontend/c04-communicate.md +54 -0
  38. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/micro-frontend/c05-mixed-stack.md +24 -0
  39. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/_category_.json +4 -0
  40. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/auto-actions.md +90 -0
  41. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/computed-state.md +151 -0
  42. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/define-model.md +66 -0
  43. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/faq.md +43 -0
  44. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/manage-effects.md +259 -0
  45. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/model-communicate.md +219 -0
  46. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/performance.md +173 -0
  47. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/quick-start.md +116 -0
  48. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/redux-integration.md +21 -0
  49. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/test-model.md +43 -0
  50. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/typescript-best-practice.md +71 -0
  51. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/use-model.md +244 -0
  52. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/model/use-out-of-modernjs.md +51 -0
  53. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/_category_.json +5 -0
  54. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/c01-start.md +99 -0
  55. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/c02-component.md +56 -0
  56. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/c03-css.md +324 -0
  57. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/c04-routes.md +169 -0
  58. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/c05-loader.md +82 -0
  59. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/c06-model.md +260 -0
  60. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/c07-container.md +283 -0
  61. package/en/docusaurus-plugin-content-docs/current/tutorials/first-app/c08-entries.md +137 -0
  62. package/en/docusaurus-plugin-content-docs/current/tutorials/foundations/_category_.json +1 -1
  63. package/en/docusaurus-plugin-content-docs/current/tutorials/foundations/introduction.md +5 -3
  64. package/package.json +4 -4
  65. package/zh/apis/app/runtime/core/use-module-apps.md +2 -0
  66. package/zh/apis/app/runtime/router/router.md +169 -371
  67. package/zh/components/micro-master-manifest-config.md +15 -0
  68. package/zh/components/router-legacy-tip.md +1 -0
  69. package/zh/configure/app/auto-load-plugin.md +62 -0
  70. package/zh/configure/app/deploy/microFrontend.md +0 -10
  71. package/zh/configure/app/output/ssg.md +1 -5
  72. package/zh/configure/app/runtime/master-app.md +4 -18
  73. package/zh/configure/app/runtime/router.md +19 -4
  74. package/zh/configure/app/runtime/state.md +7 -7
  75. package/zh/configure/app/server/enable-framework-ext.md +47 -0
  76. package/zh/configure/app/server/port.md +1 -1
  77. package/zh/configure/app/tools/_category_.json +1 -1
  78. package/zh/guides/advanced-features/eslint.md +2 -1
  79. package/zh/guides/advanced-features/ssg.md +4 -0
  80. package/zh/guides/basic-features/data-fetch.md +1 -1
  81. package/zh/guides/basic-features/env-vars.md +1 -1
  82. package/zh/guides/basic-features/routes.md +0 -3
  83. package/zh/guides/topic-detail/generator/config/module.md +3 -1
  84. package/zh/guides/topic-detail/generator/config/mwa.md +1 -9
  85. package/zh/guides/topic-detail/model/quick-start.md +1 -1
  86. package/zh/tutorials/first-app/c06-model.md +5 -1
  87. package/zh/tutorials/first-app/c08-entries.md +1 -1
  88. package/zh/tutorials/foundations/introduction.md +5 -3
  89. package/en/docusaurus-plugin-content-docs/current/apis/app/overview.md +0 -12
  90. package/en/docusaurus-plugin-content-docs/current/configure/app/bff/fetcher.md +0 -28
  91. package/en/docusaurus-plugin-content-docs/current/configure/app/dev/with-master-app.md +0 -31
  92. package/en/docusaurus-plugin-content-docs/current/guides/overview.md +0 -11
  93. package/en/docusaurus-plugin-content-docs/current/tutorials/foundations/basic.md +0 -8
  94. package/zh/apis/app/overview.md +0 -11
  95. package/zh/apis/monorepo/overview.md +0 -11
  96. package/zh/configure/app/bff/fetcher.md +0 -31
  97. package/zh/configure/app/dev/with-master-app.md +0 -32
  98. package/zh/guides/overview.md +0 -11
  99. package/zh/tutorials/foundations/basic.md +0 -8
@@ -0,0 +1,219 @@
1
+ ---
2
+ sidebar_position: 7
3
+ title: Model 通信
4
+ ---
5
+
6
+ Model 通信,既指不同 Model 间的通信,也指同一个 Model 内部 Effects、Actions 之间的通信。
7
+
8
+ ## Model 间通信
9
+
10
+ Model 之间不是孤立的,是可以进行通信的。主要分为两种场景:
11
+
12
+ 1. 在 Model 中访问其它 Model 的 State 和 Actions。
13
+ 2. 在 Model 中监听其它 Model 变化。
14
+
15
+ 这里将 [快速上手](/docs/guides/topic-detail/model/quick-start) 一节的简单计数器应用改造成一个可设置步频的计数器应用。可以通过设置步频,从而影响每次计数器增加的幅度。
16
+
17
+ 我们抽象出两个 Model,分别为 `stepModel`(步频)、`counterModel`(计数器):
18
+
19
+ ```ts
20
+ import { model } from '@modern-js/runtime/model';
21
+
22
+ const stepModel = model('step').define({
23
+ state: 1,
24
+ });
25
+
26
+ const counterModel = model('count').define((context, { use, onMount }) => {
27
+ const [, , subscribeStep] = use(stepModel);
28
+
29
+ onMount(() => {
30
+ return subscribeStep(() => {
31
+ console.log(
32
+ `Subscribe in counterModel: stepModel change to ${use(stepModel)[0]}`,
33
+ );
34
+ });
35
+ });
36
+
37
+ return {
38
+ state: {
39
+ value: 1,
40
+ },
41
+ actions: {
42
+ add(state) {
43
+ const step = use(stepModel)[0];
44
+ state.value += step;
45
+ },
46
+ },
47
+ };
48
+ });
49
+
50
+ export { stepModel, counterModel };
51
+ ```
52
+
53
+ `stepModel` 只声明一个 `state`,初始值为 1。
54
+
55
+ `counterModel` 通过 `use` 函数加载 `stepModel`,拿到返回的 `subscribeStep` 函数,用来监听 `stepModel` 状态的变更。 `onMount` 是 Model 挂载完成后的钩子函数,`counterModel` 挂载完成后开始订阅 `stepModel` 状态的变更,打印出 `stepModel` 的最新值。
56
+
57
+ `counterModel` 通过 `use` 函数访问 `stepModel`,在 `add` 里可以获取到当前 `stepModel` 的值(步频),以此值来做自增。
58
+
59
+ :::caution 注意
60
+ 当需要访问其他 Model 的 State 时,必须要在当前 Actions 或 Effects 函数(本例中对应 `add` 函数 )真正执行的阶段调用 `use`,以保证获取的 State 是最新值。因此,我们虽然在 `define` 的回调函数中也调用了 `use(stepModel)`,但是我们并没有解构 `stepModel` 的 `state` 值,因为 `define` 的回调函数是在 Model 的挂载阶段执行的,这个时候获取到的 `stepModel` 的 `state` 可能和 `add` 执行时获取到的值是不同的。
61
+ :::
62
+
63
+ 修改 **App.tsx**
64
+
65
+ ```tsx
66
+ import { useModel } from '@modern-js/runtime/model';
67
+ import { counterModel, stepModel } from './models/count';
68
+
69
+ function Counter() {
70
+ const [state, actions] = useModel(counterModel);
71
+ const [step, stepActions] = useModel(stepModel);
72
+
73
+ return (
74
+ <div>
75
+ <div>step: {step}</div>
76
+ <button onClick={() => stepActions.setState(step + 1)}>add step</button>
77
+ <div>counter: {state.value}</div>
78
+ <button onClick={() => actions.add()}>add counter</button>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ export default function App() {
84
+ return <Counter />;
85
+ }
86
+ ```
87
+
88
+ :::info 补充信息
89
+ Modern.js 默认开启 [自动生成 actions](./auto-actions.md),所以 `stepModel` 中虽然没有手动定义 Actions,但可以使用自动生成的 `setState`。
90
+ :::
91
+
92
+ - 点击 **add step** 增加步频。
93
+ - 点击 **add counter** 触发计数器增加。
94
+
95
+ 最终效果如下:
96
+
97
+ ![communicate-models](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/models-communicate.gif)
98
+
99
+
100
+ :::info 补充信息
101
+ - 本节完整的[示例代码](https://github.com/modern-js-dev/modern-js-examples/tree/main/series/tutorials/runtime-api/model/models-communication)。
102
+ - 相关 API 的更多介绍,请参考:[model](/docs/apis/app/runtime/model/model_#函数类型)。
103
+ :::
104
+
105
+ 前面 `counterModel` 的例子,我们是在 Actions 的函数内部调用 `use` 获取其他 Model 对象的。如果只需要调用其它 Model 的 Actions,因为 Actions 都是函数,不存在值过期问题,所以也可以在 `define` 的回调函数中调用 `use` 获取 Model 的 Actions。例如:
106
+
107
+ ```ts
108
+ const barModel = model('bar').define({
109
+ // 省略
110
+ });
111
+
112
+ const fooModel = model('foo').define((context, utils) => {
113
+ // 获取 barModel 的 actions
114
+ const [, actions] = utils.use(barModel);
115
+ return {
116
+ // 省略 state、actions
117
+ effects: {
118
+ async loadA() {
119
+ // 省略副作用逻辑
120
+ // 调用 barModel 的 action
121
+ barModel.actionA();
122
+ },
123
+ async loadB() {
124
+ // 省略副作用逻辑
125
+ // 调用 barModel 的 action
126
+ barModel.actionB();
127
+ },
128
+ },
129
+ };
130
+ });
131
+ ```
132
+
133
+ 这样,我们不需要在 `loadA`、`loadB` 中重复获取 `barModel` 对象,简化了代码逻辑。
134
+
135
+
136
+ ## Model 内通信
137
+
138
+ Model 内通信,也主要分为两种场景:
139
+
140
+ 1. Effects 函数调用自身 Model 的 Actions 函数、或其他 Effects 函数。
141
+ 2. Actions 函数调用自身 Model 的 其他 Actions 函数。
142
+
143
+ 在 [副作用管理](/docs/guides/topic-detail/model/manage-effects) 一节,我们演示过 Effects 函数如何调用 Actions 函数。
144
+
145
+ 这里我们再来举一个例子:
146
+
147
+ ```ts
148
+ const fooModel = model('foo').define((context, { use, onMount }) => ({
149
+ state: {
150
+ a: '',
151
+ b: '',
152
+ },
153
+ actions: {
154
+ setA(state, payload) {
155
+ state.a = payload;
156
+ },
157
+ setB(state, payload) {
158
+ state.a = payload;
159
+ },
160
+ },
161
+ effects: {
162
+ async loadA() {
163
+ // 通过 use 获取当前 Model 的 actions
164
+ const [, actions] = use(fooModel);
165
+ const res = await mockFetchA();
166
+ actions.setA(res);
167
+ },
168
+ async loadB() {
169
+ // 通过 use 获取当前 Model 的 actions
170
+ const [, actions] = use(fooModel);
171
+ const res = await mockFetchB();
172
+ actions.setB(res);
173
+ },
174
+ },
175
+ }));
176
+ ```
177
+
178
+ 这个例子中,`fooModel` 的两个 Effects 函数,需要调用自身的 Actions 函数。这里我们在每个 Effects 函数中,都调用了一次 `use`,为什么不能像 Model 间通信的例子中,在 `define` 的回调函数中,统一调用 `use` 获取 Model 自身的 Actions 呢?这是因为调用 `use` 获取 Model 时,会先检查这个 Model 是否已经挂载,如果还没有挂载,会先执行挂载逻辑,而 `define` 的回调函数又是在 Model 的挂载阶段执行的,这样一来,在挂载阶段调用 `use` 获取 Model 自身,会出现死循环(代码实际执行过程会抛出错误)。所以,**一定不能在 `define` 的回调函数中,调用 `use` 获取 Model 自身对象。**
179
+
180
+ 不过,我们可以利用 `onMount` 这个钩子函数,在 Model 挂载完成后,再通过 `use` 获取 Model 自身对象:
181
+
182
+ ```ts
183
+ const fooModel = model('foo').define((context, { use, onMount }) => {
184
+ let actions;
185
+
186
+ onMount(() => {
187
+ // fooModel 挂载完成后,通过 use 获取当前 Model 的 actions
188
+ [, actions] = use(fooModel);
189
+ });
190
+
191
+ return {
192
+ state: {
193
+ a: '',
194
+ b: '',
195
+ },
196
+ actions: {
197
+ setA(state, payload) {
198
+ state.a = payload;
199
+ },
200
+ setB(state, payload) {
201
+ state.a = payload;
202
+ },
203
+ },
204
+ effects: {
205
+ async loadA() {
206
+ const res = await mockFetchA();
207
+ actions.setA(res);
208
+ },
209
+ async loadB() {
210
+ const res = await mockFetchB();
211
+ actions.setB(res);
212
+ },
213
+ },
214
+ };
215
+ });
216
+ ```
217
+
218
+ 这样,我们也可以实现代码的简化。
219
+
@@ -0,0 +1,173 @@
1
+ ---
2
+ sidebar_position: 8
3
+ title: 性能优化
4
+ ---
5
+
6
+ Reduck 内部已经做了大量性能优化工作,一般情况下不需要考虑性能问题。不过当对性能比较敏感、或者遇到了性能问题,可以考虑从以下 3 个方面,进行更有针对性的性能优化。
7
+
8
+
9
+ ## Model 拆分
10
+
11
+ 当 `useModel` 返回 Model 对象的完整 State 时,State 任意部分的变化都会导致调用了 `useModel` 的组件重新渲染。
12
+
13
+ 例如:
14
+
15
+ ```ts
16
+ const fooModel = model('foo').define({
17
+ state: {
18
+ a: '',
19
+ b: '',
20
+ },
21
+ actions: {
22
+ setA(state, payload) {
23
+ state.a = payload;
24
+ },
25
+ setB(state, payload) {
26
+ state.b = payload;
27
+ },
28
+ },
29
+ });
30
+
31
+ function ComponentA() {
32
+ const [state] = useModel(fooModel);
33
+
34
+ return <div>{state.a}</div>;
35
+ }
36
+ ```
37
+
38
+ 组件 `ComponentA` 虽然只需要使用 `a` 状态,但当 `b` 状态发送变化时, `ComponentA` 仍然会重新渲染。这种情况,我们可以考虑把 `fooModel` 拆分,`a`、`b` 分别由不同的 Model 负责管理:
39
+
40
+ ```ts
41
+ const fooModel = model('foo').define({
42
+ state: {
43
+ a: '',
44
+ },
45
+ actions: {
46
+ setA(state, payload) {
47
+ state.a = payload;
48
+ },
49
+ },
50
+ });
51
+
52
+ const barModel = model('bar').define({
53
+ state: {
54
+ b: '',
55
+ },
56
+ actions: {
57
+ setB(state, payload) {
58
+ state.b = payload;
59
+ },
60
+ },
61
+ });
62
+
63
+ ```
64
+
65
+
66
+ ## 状态筛选
67
+
68
+ `useModel` 支持传入 selector 函数,对返回给组件的 State 和 Actions 做筛选。我们可以通过 selector 函数,确保返回给组件的 State 是组件直接需要使用的,从而保证组件不会因为其他无关状态的变化而重新渲染。
69
+
70
+ 对于上面同样的例子,我们采用 selector 函数进行性能优化,代码如下:
71
+
72
+ ```ts
73
+ const fooModel = model('foo').define({
74
+ state: {
75
+ a: '',
76
+ b: '',
77
+ },
78
+ actions: {
79
+ setA(state, payload) {
80
+ state.a = payload;
81
+ },
82
+ setB(state, payload) {
83
+ state.b = payload;
84
+ },
85
+ },
86
+ });
87
+
88
+ function ComponentA() {
89
+ // 通过传入 selector 函数,只返回 a 状态给组件
90
+ const [stateA] = useModel(fooModel, state => state.a);
91
+
92
+ return <div>{stateA}</div>;
93
+ }
94
+ ```
95
+
96
+ ## 衍生状态缓存
97
+
98
+ 当 Model 存在 `computed` 时,每次调用`useModel` 都会执行 `computed` 函数。
99
+
100
+ 考虑如下代码:
101
+
102
+ ```ts
103
+ const barModel = model('bar').define({
104
+ state: {
105
+ value: 'bar',
106
+ },
107
+ computed: {
108
+ combineA: [
109
+ fooModel, // fooModel 定义同上
110
+ (state, fooState) => {
111
+ return state + fooState.a;
112
+ },
113
+ ],
114
+ },
115
+ actions: {
116
+ setValue(state, payload) {
117
+ state.value = payload;
118
+ },
119
+ },
120
+ });
121
+
122
+ function ComponentB() {
123
+ const [state, actions] = useModel(fooModel);
124
+ const [{ combineA }] = useModel(barModel);
125
+ // 省略
126
+ }
127
+ ```
128
+
129
+ `barModel` 的衍生状态 `combineA` 依赖 `barModel` 自身状态 和 `fooModel` 的状态 `a`,但是即使是 `fooModel` 的状态 `b` 发生了变化,组件重新渲染时, `combineA` (更准确的说法是 `combineA` 的最后一个函数类型的元素 )依然会被调用执行。
130
+
131
+ 一般情况下,`computed` 函数中的逻辑都是非常轻量的,但当 `computed` 函数逻辑比较复杂时,我们可以考虑对计算逻辑做缓存。例如,我们使用 [reselect](https://github.com/reduxjs/reselect) 对 `barModel` 的 `combineA` 做缓存:
132
+
133
+ ```ts
134
+ import 'createSelector' from 'reselect';
135
+
136
+ // 创建缓存函数
137
+ const selectCombineA = createSelector(
138
+ (state) => state.bar.value,
139
+ (state) => state.foo.a,
140
+ (barState, fooState) => {
141
+ return barState + fooState;
142
+ }
143
+ );
144
+
145
+ const barModel = model("bar").define({
146
+ state: {
147
+ value: "bar",
148
+ },
149
+ computed: {
150
+ combineA: [
151
+ fooModel,
152
+ (state, fooState) => {
153
+ return selectCombineA({
154
+ foo: fooState,
155
+ bar: state,
156
+ });
157
+ },
158
+ ],
159
+ },
160
+ actions: {
161
+ setValue(state, payload) {
162
+ state.value = payload;
163
+ },
164
+ },
165
+ });
166
+ ```
167
+
168
+ 我们创建缓存函数 `createSelector`,仅当 `barModel` 的状态发生改变或 `fooModel` 的 `a` 状态发生改变时,才会重新计算 `combineA` 的值。
169
+
170
+
171
+ :::info 补充信息
172
+ - 本节完整的[示例代码](https://github.com/modern-js-dev/modern-js-examples/tree/main/series/tutorials/runtime-api/model/performance-optimization)
173
+ :::
@@ -0,0 +1,116 @@
1
+ ---
2
+ sidebar_position: 1
3
+ title: 快速上手
4
+ ---
5
+
6
+ import ReduckMigration from '@site-docs/components/reduck-migration.md'
7
+
8
+ <ReduckMigration />
9
+
10
+ [Reduck](https://github.com/modern-js-dev/reduck) 是 Modern.js 团队开发的遵循 MVC 模式的状态管理库,底层状态存储基于 [Redux](https://redux.js.org/) 实现,同时提供更高层级的抽象,并完全兼容 Redux 生态。
11
+
12
+ Reduck 的目标是以 MVC 模式组织 React 应用开发结构,将业务逻辑维护在 Model 层,业务逻辑与 UI 解耦,让开发业务逻辑更集中、更简单,同时通过更高层级的抽象,减少重复工作(样板代码)。
13
+
14
+ Reduck 在 MVC 模式中,扮演 M(Model) 的角色,React UI Component 对应 V(View),从 Reduck 中获取 Model 并修改 Model 的 React Container Component 对应 C(View Controller/Container)。
15
+
16
+ Modern.js 的状态管理解决方案,是通过内置 Reduck 实现的。在 Modern.js 中使用 Reduck,不仅免去了手动集成的环节,而且所有 Reduck API 都可以从 Modern.js 的 Runtime 包中直接导入使用,具有更好的一致性体验。
17
+
18
+ :::info 注
19
+ 1. Modern.js 中使用 Reduck API,需要先设置 [runtime.state](/docs/configure/app/runtime/state) 以启用状态管理插件。
20
+ 2. Reduck 也可以脱离 Modern.js 作为状态管理库[单独使用](/docs/guides/topic-detail/model/use-out-of-modernjs)。
21
+ :::
22
+
23
+
24
+ ## 核心概念
25
+
26
+ Reduck 中的核心概念只有 4 个: Model、State、Actions、Effects。
27
+
28
+ Model: 对独立模块的逻辑和所需状态的封装,由 State、Actions、Effects 组成。
29
+
30
+ State: Model 中保存的状态。
31
+
32
+ Actions: 用于修改 State 的纯函数,函数必须是**同步**的。
33
+
34
+ Effects: 用于修改 State 的带有副作用的函数,函数可以是**异步**的。Effects 中可以调用自身或其他 Model 的 Actions 和 Effects。
35
+
36
+ Reduck 数据流如下图所示:
37
+
38
+ ![Reduck 数据流](https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/reduck-concept.svg)
39
+
40
+ ## 基本用法
41
+
42
+ 下来我们以一个简单的 **计数器** 应用为例,演示 Reduck 的基本用法。
43
+
44
+ 首先,我们定义一个名为 `count` 的 Model:
45
+
46
+ ```js
47
+ import { model } from '@modern-js/runtime/model';
48
+
49
+ const countModel = model('count').define({
50
+ state: {
51
+ value: 1,
52
+ },
53
+ });
54
+
55
+ export default countModel;
56
+ ```
57
+
58
+ 我们使用 API `model` 创建 `countModel`,`countModel` 当前只包含存储计数器值的状态,即代码中的 `value`。
59
+
60
+ 我们定义一个 action,用于计算器自增加 1:
61
+
62
+ ```js
63
+ import { model } from '@modern-js/runtime/model';
64
+
65
+ const countModel = model('count').define({
66
+ state: {
67
+ value: 1,
68
+ },
69
+ actions: {
70
+ add(state) {
71
+ state.value += 1;
72
+ },
73
+ },
74
+ });
75
+
76
+ export default countModel;
77
+ ```
78
+
79
+ 在 `add` action 中,我们可以直接修改 state 的值,进行加 1 操作,而不需要把 state 作为不可变对象进行操作,这是因为 Reduck 集成了 [immer](https://github.com/immerjs/immer),可以直接修改原 state 对象。
80
+
81
+
82
+ 接下来,我们演示如何在组件中使用 Model。
83
+
84
+ 新建一个组件 Counter,在组件内通过 `useModel` API 使用 `countModel`:
85
+
86
+ ```js
87
+ import { useModel } from '@modern-js/runtime/model';
88
+ import countModel from './models/count';
89
+
90
+ function Counter() {
91
+ const [state, actions] = useModel(countModel);
92
+
93
+ return (
94
+ <div>
95
+ <div>counter: {state.value}</div>
96
+ <button onClick={() => actions.add()}>add</button>
97
+ </div>
98
+ );
99
+ }
100
+ ```
101
+
102
+ `useModel` 获取 `countModel` 的 `state` 和 `actions`,组件展示当前计算器的值,点击 `add` 按钮,计数器自增 1。
103
+
104
+ :::info 注
105
+ 由于使用的案例比较简单,这里并没有严格按照 MVC 模式进行分层,组件 `Counter` 同时起到了 V 和 C 两层的作用。
106
+ :::
107
+
108
+
109
+
110
+ 最终演示效果如下:
111
+
112
+ ![countModel](https://lf3-static.bytednsdoc.com/obj/eden-cn/eueh7vhojuh/modern/simple-count-model.gif)
113
+
114
+
115
+ 这样,我们就完了一个简单的计数器应用。本节完整的示例代码可以在[这里](https://github.com/modern-js-dev/modern-js-examples/tree/main/series/tutorials/runtime-api/model/counter-model)查看。
116
+
@@ -0,0 +1,21 @@
1
+ ---
2
+ sidebar_position: 11
3
+ title: Redux 生态集成
4
+ ---
5
+
6
+ Reduck 基于 Redux 实现,因此可以使用 Redux [生态的库](https://redux.js.org/introduction/ecosystem),实现功能增强。通过 [`Provider`](/docs/apis/app/runtime/model/Provider) 、[`createApp`](/docs/apis/app/runtime/model/create-app) 和 [`createStore`](/docs/apis/app/runtime/model/create-store) 等 API ,可以设置使用 Redux 的 [中间件](https://redux.js.org/understanding/thinking-in-redux/glossary#middleware) 和 [Store Enhancer](https://redux.js.org/understanding/thinking-in-redux/glossary#store-enhancer);使用 [`createStore`](/docs/apis/app/runtime/model/create-store) 还可以完全掌控 Store 的创建过程。
7
+
8
+ 例如,我们希望使用中间件 [`redux-logger`](https://github.com/LogRocket/redux-logger),示例代码如下:
9
+
10
+ ```ts
11
+ ReactDOM.render(
12
+ <Provider config={{ middlewares: [logger] }}> // 通过 Provider 的 config 参数设置 中间件
13
+ <App />
14
+ </Provider>,
15
+ document.getElementById('root')
16
+ );
17
+ ```
18
+
19
+ :::caution
20
+ Reduck 基于 Redux 底层 API 做了上层封装,屏蔽了 Redux 的一些底层概念,如 Reducer 等。Reduck 对于 Model 是动态挂载的,而 Redux 是在 Store 创建时就会挂载应用所需的全部状态。基于这些实现上的差异,有些 Redux 生态的库是无法直接在 Reduck 中使用的。
21
+ :::
@@ -0,0 +1,43 @@
1
+ ---
2
+ sidebar_position: 9
3
+ title: 测试 Model
4
+ ---
5
+
6
+ 好的测试对代码的稳健性至关重要。下面以 [快速上手](/docs/guides/topic-detail/model/quick-start) 的 `countModel` 为例,演示在 Modern.js 中,如何对 Model 进行单元测试。
7
+
8
+ 使用测试功能,需要先开启该功能。在项目根目录下,执行 `pnpm run new`,进行如下选择:
9
+
10
+ ```bash
11
+ ? 请选择你想要的操作 启用可选功能
12
+ ? 启用可选功能 启用「单元测试 / 集成测试」功能
13
+ ```
14
+
15
+ 即可开启测试功能支持。
16
+
17
+ 新增 `count.test.ts` 文件,代码如下:
18
+
19
+ ```ts
20
+ import { createStore } from '@modern-js/runtime/testing';
21
+ import countModel from './count';
22
+
23
+ describe('test model', () => {
24
+ it('count value should plus one after add', () => {
25
+ const store = createStore();
26
+ const [state, { add }] = store.use(countModel);
27
+
28
+ expect(state).toEqual({ value: 1 });
29
+
30
+ add();
31
+
32
+ expect(store.use(countModel)[0]).toEqual({ value: 2 });
33
+ });
34
+ });
35
+ ```
36
+ :::info 注
37
+ 这里使用的 [`createStore`](/docs/apis/app/runtime/model/create-store) 是从 `@modern-js/runtime/testing` 导入的,内部会使用 [`runtime.state`](/docs/configure/app/runtime/state) 的配置去创建 `store`。
38
+ :::
39
+
40
+ 在测试用例里,我们新建一个 `store` 来挂载 `countModel`,通过 `store.use` 获取 `countModel` 的 State 和 Actions。然后调用 `add` Action 更新状态,并断言更新后的状态值。
41
+
42
+ 执行 `pnpm run test` 命令,触发测试用例的执行。
43
+
@@ -0,0 +1,71 @@
1
+ ---
2
+ sidebar_position: 10
3
+ title: TS 最佳实践
4
+ ---
5
+
6
+ Reduck 对 TS 提供了良好的支持,大部分使用场景下,无需任何额外工作,就可以直接获得 API 的 TS 类型提示。本节,将对其他的一些使用场景,做补充介绍。
7
+
8
+ ## 定义 Model 的 State 类型
9
+
10
+ 为 Model 的 State 声明类型信息,是在 TS 中使用 Reduck 的最佳实践。
11
+
12
+ ```ts title="示例"
13
+ interface State {
14
+ data: string
15
+ }
16
+
17
+ export const foo = model<State>('foo').define({
18
+ state: {
19
+ data: ''
20
+ },
21
+ computed: {
22
+ withSuffix: (state) => state.data + 'suffix'
23
+ },
24
+ actions: {
25
+ setData: (state, payload:string) => {
26
+ state.data = payload
27
+ }
28
+ }
29
+ })
30
+ ```
31
+
32
+ 当为 Model 的 State 声明类型信息后,Model 的 `computed`、`actions` 都能获取正确的类型信息。事实上,上面的示例代码中,即使我们不定义 State 类型信息,也会根据 `state` 的初始值信息自动推导出 State 的类型信息。不过,仍然建议你在定义 Model 时,声明 State 的类型信息,因为根据 `state` 的初始值信息推导出的 State 类型信息可能不完整(缺少字段或字段的类型信息缺少),而且当使用[函数类型](/docs/apis/app/runtime/model/model_#函数类型)定义 Model 时,State 的类型信息也是无法根据 `state` 的初始值信息自动推导的。
33
+
34
+ ## 衍生状态的依赖类型
35
+
36
+ 当 Model 的衍生状态依赖其他 Model 时,需要手动指定其他 Model 的 State。
37
+
38
+
39
+ ```ts title="示例"
40
+ interface State {
41
+ data: string
42
+ }
43
+
44
+ export const bar = model<State>('bar').define({
45
+ state: {
46
+ data: ''
47
+ },
48
+ computed: {
49
+ // 为 fooState 指定类型
50
+ withFoo: [foo, (state, fooState: FooState) => state.data + fooState.data]
51
+ },
52
+ })
53
+ ```
54
+
55
+ ## 获取 Model 类型信息的 Hooks
56
+
57
+ Reduck 提供了一组用于获取 Model 类型信息的工具类型:
58
+
59
+ - `GetModelState`: 获取 Model 的 State(包含衍生状态)类型信息。
60
+ - `GetModelActions`:获取 Model 的 Actions(包含 Effects 函数)类型信息。
61
+
62
+ ```ts title="示例"
63
+ export const foo = model<State2>('foo').define({
64
+ // 省略
65
+ })
66
+
67
+ // 获取 foo 的 State 类型
68
+ let fooActions: GetModelActions<typeof foo>;
69
+ // 获取 foo 的 Actions 类型
70
+ let fooState: GetModelState<typeof foo>;
71
+ ```