@modern-js/main-doc 0.0.0-next-20221203140534 → 0.0.0-next-20221205074243
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.
- package/CHANGELOG.md +2 -2
- package/en/docusaurus-plugin-content-docs/current/apis/app/commands/new.md +0 -2
- package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/bootstrap.md +17 -3
- package/en/docusaurus-plugin-content-docs/current/guides/basic-features/builder.md +46 -0
- package/en/docusaurus-plugin-content-docs/current.json +18 -18
- package/package.json +3 -3
- package/zh/apis/app/commands/new.md +0 -2
- package/zh/apis/app/runtime/app/_category_.json +1 -1
- package/zh/apis/app/runtime/bff/_category_.json +1 -1
- package/zh/apis/app/runtime/core/_category_.json +1 -1
- package/zh/apis/app/runtime/core/bootstrap.md +17 -3
- package/zh/apis/app/runtime/model/_category_.json +1 -1
- package/zh/apis/app/runtime/router/_category_.json +1 -1
- package/zh/apis/app/runtime/ssr/_category_.json +1 -1
- package/zh/apis/app/runtime/testing/_category_.json +1 -1
- package/zh/apis/app/runtime/utility/_category_.json +1 -1
- package/zh/apis/app/runtime/web-server/_category_.json +1 -1
- package/zh/configure/app/output/ssg.md +118 -114
- package/zh/configure/app/server/ssr.md +0 -2
- package/zh/guides/advanced-features/custom-app.md +8 -2
- package/zh/guides/advanced-features/ssg.md +74 -63
- package/zh/guides/advanced-features/ssr.md +76 -11
- package/zh/guides/basic-features/builder.md +46 -0
- package/zh/guides/basic-features/css/_category_.json +1 -1
- package/zh/guides/basic-features/css/less-sass.md +1 -14
- package/zh/guides/basic-features/data-fetch.md +1 -1
- package/zh/guides/basic-features/routes.md +32 -35
- package/zh/guides/concept/entries.md +4 -4
- package/zh/tutorials/first-app/c01-getting-started/1.2-minimal-mwa.md +2 -2
- package/zh/tutorials/first-app/c01-getting-started/1.4-enable-ssr.md +5 -2
- package/zh/tutorials/first-app/c02-generator-and-studio/2.2-boilerplates.md +4 -6
- package/zh/tutorials/first-app/c02-generator-and-studio/2.3-configuration.md +2 -4
- package/zh/tutorials/first-app/c03-ide/3.1-setting-up.md +1 -1
- package/zh/tutorials/first-app/c03-ide/3.2-hints-in-ide.md +44 -50
- package/zh/tutorials/first-app/c03-ide/3.3-autofix-in-ide.md +1 -1
- package/zh/tutorials/first-app/c03-ide/3.4-autofix-in-cli.md +4 -4
- package/zh/tutorials/first-app/c04-es6-plus-and-ts/4.1-use-es6-plus.md +8 -21
- package/zh/tutorials/first-app/c04-es6-plus-and-ts/4.2-use-typescript.md +37 -13
- package/zh/tutorials/first-app/c05-component/5.1-use-ui-library.md +3 -13
- package/zh/tutorials/first-app/c05-component/5.2-use-standalone-component.md +1 -21
- package/zh/tutorials/first-app/c06-css-and-component/6.1-css-in-js.md +9 -9
- package/zh/tutorials/first-app/c06-css-and-component/6.2-utility-class.md +9 -14
- package/zh/tutorials/first-app/c06-css-and-component/6.3-postcss.md +7 -7
- package/zh/tutorials/first-app/c06-css-and-component/6.4-design-system.md +1 -1
- package/zh/tutorials/first-app/c06-css-and-component/6.5-storybook.md +2 -2
- package/zh/tutorials/first-app/c06-css-and-component/6.6-testing.md +8 -17
- package/zh/tutorials/first-app/c07-app-entry/7.1-intro.md +23 -18
- package/zh/tutorials/first-app/c07-app-entry/7.2-add-entry-in-cli.md +30 -30
- package/zh/tutorials/first-app/c07-app-entry/7.3-manage-entries-by-hand.md +4 -9
- package/zh/tutorials/first-app/c08-client-side-routing/8.1-code-based-routing.md +66 -63
- package/zh/tutorials/first-app/c09-bff/9.2-enable-bff.md +35 -33
- package/zh/tutorials/first-app/c09-bff/9.3-fetch-bff.md +28 -102
- package/zh/tutorials/first-app/c10-model/10.1-application-architecture.md +4 -6
- package/zh/tutorials/first-app/c10-model/10.2-add-model.md +3 -3
- package/zh/tutorials/first-app/c10-model/10.3-use-model.md +21 -20
- package/zh/tutorials/first-app/c10-model/10.4-testing.md +2 -2
- package/zh/tutorials/first-app/c11-container/11.1-use-model-with-app-state.md +34 -68
- package/zh/tutorials/first-app/c11-container/11.2-add-container.md +40 -37
- package/zh/tutorials/first-app/c11-container/11.3-use-loader.md +6 -4
- package/zh/tutorials/first-app/c11-container/11.4-testing.md +2 -2
- package/zh/tutorials/foundations/introduction.md +1 -1
- package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/default-alias.md +0 -25
- package/zh/apis/app/runtime/default-alias.md +0 -23
- package/zh/guides/basic-features/image.md +0 -43
- package/zh/guides/topic-detail/compile-speed.md +0 -182
- package/zh/guides/troubleshooting/compile.md +0 -379
- package/zh/tutorials/first-app/c08-client-side-routing/8.2-file-based-routing.md +0 -310
@@ -1,5 +1,5 @@
|
|
1
1
|
---
|
2
|
-
title:
|
2
|
+
title: 使用约定式路由
|
3
3
|
---
|
4
4
|
|
5
5
|
上一章节中,我们学习了如何创建应用入口。
|
@@ -21,55 +21,53 @@ export default defineConfig({
|
|
21
21
|
|
22
22
|
之前我们已经为联系人列表增加了 Archive 按钮,接下来我们添加一个客户端路由 `/archives`,访问这个路由时,只显示已存档的联系人,而原有的 `/` 继续显示所有联系人。
|
23
23
|
|
24
|
-
|
24
|
+
新建 `src/contacts/routes/archives/page.tsx`, 添加如下代码:
|
25
25
|
|
26
|
-
```
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
26
|
+
```ts
|
27
|
+
import { List } from 'antd';
|
28
|
+
import { Helmet } from '@modern-js/runtime/head';
|
29
|
+
import Item from '../components/Item';
|
30
|
+
|
31
|
+
const getAvatar = (users: Array<{ name: string; email: string }>) =>
|
32
|
+
users.map(user => ({
|
33
|
+
...user,
|
34
|
+
avatar: `https://avatars.dicebear.com/v2/identicon/${user.name}.svg`,
|
35
|
+
}));
|
36
|
+
|
37
|
+
const getMockArchivedData = () =>
|
38
|
+
getAvatar([
|
39
|
+
{ name: 'Thomas', email: 'w.kccip@bllmfbgv.dm' },
|
40
|
+
{ name: 'Chow', email: 'f.lfqljnlk@ywoefljhc.af' },
|
41
|
+
]);
|
42
|
+
function Index() {
|
43
|
+
return (
|
44
|
+
<div className="container lg mx-auto">
|
45
|
+
<Helmet>
|
46
|
+
<title>Archives</title>
|
47
|
+
</Helmet>
|
48
|
+
<List
|
49
|
+
dataSource={getMockArchivedData()}
|
50
|
+
renderItem={info => <Item key={info.name} info={info} />}
|
51
|
+
/>
|
52
|
+
</div>
|
53
|
+
);
|
54
|
+
}
|
55
|
+
|
56
|
+
export default Index;
|
38
57
|
```
|
39
58
|
|
40
|
-
|
59
|
+
这里使用了 [React Helmet](https://github.com/nfl/react-helmet) 的 `Helmet` 组件,在 `src/contacts/routes/page.tsx` 中也添加 Helmet 组件:
|
41
60
|
|
42
|
-
```
|
43
|
-
import { Route, Switch } from '@modern-js/runtime/router';
|
61
|
+
```tsx
|
44
62
|
import { Helmet } from '@modern-js/runtime/head';
|
45
|
-
```
|
46
63
|
|
47
|
-
在 `App` 组件中使用 `Route` 写两个路由,分别用不同的 mock 数据渲染列表:
|
48
|
-
|
49
|
-
```js
|
50
64
|
function App() {
|
51
65
|
return (
|
52
66
|
<div className="container lg mx-auto">
|
53
|
-
<
|
54
|
-
<
|
55
|
-
|
56
|
-
|
57
|
-
</Helmet>
|
58
|
-
<List
|
59
|
-
dataSource={mockData}
|
60
|
-
renderItem={info => <Item key={info.name} info={info} />}
|
61
|
-
/>
|
62
|
-
</Route>
|
63
|
-
<Route path="/archives" exact={true}>
|
64
|
-
<Helmet>
|
65
|
-
<title>Archives</title>
|
66
|
-
</Helmet>
|
67
|
-
<List
|
68
|
-
dataSource={mockArchivedData}
|
69
|
-
renderItem={info => <Item key={info.name} info={info} />}
|
70
|
-
/>
|
71
|
-
</Route>
|
72
|
-
</Switch>
|
67
|
+
<Helmet>
|
68
|
+
<title>All</title>
|
69
|
+
</Helmet>
|
70
|
+
...
|
73
71
|
</div>
|
74
72
|
);
|
75
73
|
}
|
@@ -77,12 +75,8 @@ function App() {
|
|
77
75
|
|
78
76
|
:::info 注
|
79
77
|
Modern.js 默认集成了 react-helmet,无需安装依赖,可以直接使用,也可以结合 SSR 使用,满足 SEO 需求。
|
80
|
-
|
81
|
-
Modern.js 也默认集成了 react-router,无需安装依赖和自己配置 `BrowserRouter` 等样板代码,可以直接用 Route、Switch 等组件实现路由逻辑。
|
82
78
|
:::
|
83
79
|
|
84
|
-
React Router v4+ 有两种用法,一种是 `component-based` 的,一种是基于全局配置的。这两种都由开发者自己用代码来控制客户端路由逻辑,所以我们把这种模式称作【**自控式路由**】。
|
85
|
-
|
86
80
|
执行 `pnpm run dev`,访问 `http://localhost:8080/contacts`,可以看到完整的联系人,页面的标题是 All:
|
87
81
|
|
88
82
|

|
@@ -95,7 +89,7 @@ React Router v4+ 有两种用法,一种是 `component-based` 的,一种是
|
|
95
89
|
|
96
90
|
**接下来我们增加一个简单的导航栏,让用户能在两个列表之间切换**。
|
97
91
|
|
98
|
-
打开 src/contacts/
|
92
|
+
打开 `src/contacts/layout.tsx`,在顶部导入 Radio 组件:
|
99
93
|
|
100
94
|
```tsx
|
101
95
|
import { List, Radio } from 'antd';
|
@@ -103,39 +97,48 @@ import { List, Radio } from 'antd';
|
|
103
97
|
|
104
98
|
然后将 UI 最顶部进行修改,增加一组单选框
|
105
99
|
|
106
|
-
```tsx {
|
107
|
-
|
108
|
-
|
109
|
-
<div
|
110
|
-
<
|
111
|
-
<Radio value=
|
112
|
-
|
113
|
-
|
100
|
+
```tsx {5-8}
|
101
|
+
export default function Layout() {
|
102
|
+
return (
|
103
|
+
<div>
|
104
|
+
<div className="h-16 p-2 flex items-center justify-center">
|
105
|
+
<Radio.Group onChange={handleSetList} value={currentList}>
|
106
|
+
<Radio value="/">All</Radio>
|
107
|
+
<Radio value="/archives">Archives</Radio>
|
108
|
+
</Radio.Group>
|
109
|
+
</div>
|
110
|
+
<Outlet />
|
114
111
|
</div>
|
115
|
-
|
112
|
+
);
|
113
|
+
}
|
114
|
+
|
116
115
|
```
|
117
116
|
|
118
117
|
然后我们来实现 `currentList` 和 `handleSetList`。
|
119
118
|
|
120
|
-
|
119
|
+
引入三个 React Hook:`useState` 和 `useNavigate` 和 `useParams`,以及 Ant Design 的事件类型定义:
|
121
120
|
|
122
121
|
```js
|
123
122
|
import { useState } from 'react';
|
124
|
-
import {
|
125
|
-
import {
|
123
|
+
import { Radio, RadioChangeEvent } from 'antd';
|
124
|
+
import { Outlet, useNavigate, useParams } from '@modern-js/runtime/router';
|
126
125
|
```
|
127
126
|
|
128
|
-
最后在
|
127
|
+
最后在 Layout 组件里增加局部状态和相关逻辑:
|
129
128
|
|
130
129
|
```js {2-8}
|
131
|
-
function
|
132
|
-
const
|
133
|
-
const
|
130
|
+
export default function Layout() {
|
131
|
+
const navigate = useNavigate();
|
132
|
+
const params = useParams();
|
133
|
+
const [currentList, setList] = useState(params.pathname || '/');
|
134
134
|
const handleSetList = (e: RadioChangeEvent) => {
|
135
135
|
const { value } = e.target;
|
136
136
|
setList(value);
|
137
|
-
|
137
|
+
navigate(value);
|
138
138
|
};
|
139
|
+
return (
|
140
|
+
...
|
141
|
+
}
|
139
142
|
```
|
140
143
|
|
141
144
|
到这里就已经完成了页面导航栏实现,执行 `pnpm run dev` 查看效果:
|
@@ -18,8 +18,6 @@ title: 启用 BFF
|
|
18
18
|
# 请选择运行时框
|
19
19
|
❯ Express
|
20
20
|
Koa
|
21
|
-
Egg
|
22
|
-
Nest
|
23
21
|
```
|
24
22
|
|
25
23
|
:::info 注
|
@@ -30,43 +28,47 @@ title: 启用 BFF
|
|
30
28
|
|
31
29
|
```md
|
32
30
|
.
|
33
|
-
├── .
|
34
|
-
├── api/
|
35
|
-
│ ├── info/
|
36
|
-
│ │ └── [type].ts
|
37
|
-
│ ├── .eslintrc.json
|
38
|
-
│ ├── _app.ts
|
39
|
-
│ └── index.ts
|
40
|
-
├── src/
|
41
|
-
│ ├── contacts/
|
42
|
-
│ │ ├── components/
|
43
|
-
│ │ │ ├── Avatar/
|
44
|
-
│ │ │ │ ├── index.stories.tsx
|
45
|
-
│ │ │ │ └── index.tsx
|
46
|
-
│ │ │ └── Item/
|
47
|
-
│ │ │ ├── index.test.tsx
|
48
|
-
│ │ │ └── index.tsx
|
49
|
-
│ │ ├── styles/
|
50
|
-
│ │ │ └── utils.css
|
51
|
-
│ │ ├── App.css
|
52
|
-
│ │ └── App.tsx
|
53
|
-
│ ├── landing-page/
|
54
|
-
│ │ └── pages/
|
55
|
-
│ │ ├── comments/
|
56
|
-
│ │ │ └── [commentTitle]/
|
57
|
-
│ │ │ └── index.tsx
|
58
|
-
│ │ ├── _app.tsx
|
59
|
-
│ │ ├── docs.tsx
|
60
|
-
│ │ └── index.tsx
|
61
|
-
│ ├── .eslintrc.json
|
62
|
-
│ └── modern-app-env.d.ts
|
63
|
-
├── .editorconfig
|
31
|
+
├── .eslintrc.js
|
64
32
|
├── .gitignore
|
33
|
+
├── .husky
|
65
34
|
├── .npmrc
|
66
35
|
├── .nvmrc
|
36
|
+
├── .prettierrc
|
37
|
+
├── .vscode
|
67
38
|
├── README.md
|
39
|
+
├── api
|
40
|
+
│ ├── _app.ts
|
41
|
+
│ ├── index.ts
|
42
|
+
│ └── info
|
43
|
+
│ └── [type].ts
|
44
|
+
├── modern.config.ts
|
68
45
|
├── package.json
|
69
46
|
├── pnpm-lock.yaml
|
47
|
+
├── src
|
48
|
+
│ ├── .eslintrc.js
|
49
|
+
│ ├── contacts
|
50
|
+
│ │ ├── index.test.ts
|
51
|
+
│ │ └── routes
|
52
|
+
│ │ ├── archives
|
53
|
+
│ │ │ └── page.tsx
|
54
|
+
│ │ ├── components
|
55
|
+
│ │ │ ├── Avatar
|
56
|
+
│ │ │ │ ├── index.stories.tsx
|
57
|
+
│ │ │ │ └── index.tsx
|
58
|
+
│ │ │ └── Item
|
59
|
+
│ │ │ ├── index.test.tsx
|
60
|
+
│ │ │ └── index.tsx
|
61
|
+
│ │ ├── index.css
|
62
|
+
│ │ ├── layout.tsx
|
63
|
+
│ │ ├── page.tsx
|
64
|
+
│ │ └── styles
|
65
|
+
│ │ └── utils.css
|
66
|
+
│ ├── landing-page
|
67
|
+
│ │ └── routes
|
68
|
+
│ │ ├── index.css
|
69
|
+
│ │ ├── layout.tsx
|
70
|
+
│ │ └── page.tsx
|
71
|
+
│ └── modern-app-env.d.ts
|
70
72
|
└── tsconfig.json
|
71
73
|
```
|
72
74
|
|
@@ -35,7 +35,7 @@ mv api/index.ts api/contacts.ts
|
|
35
35
|
我们使用 [faker](https://github.com/Marak/Faker.js) 来 mock 需要的数据,首先安装依赖:
|
36
36
|
|
37
37
|
```bash
|
38
|
-
pnpm add faker
|
38
|
+
pnpm add faker@5.5.3
|
39
39
|
```
|
40
40
|
|
41
41
|
将 `api/contacts.ts` 内容改为:
|
@@ -65,69 +65,22 @@ export const get = async () => {
|
|
65
65
|
|
66
66
|

|
67
67
|
|
68
|
-
接下来我们把 `src/contacts/
|
68
|
+
接下来我们把 `src/routes/contacts/page.tsx` 里硬编码的 `getMockData` 改成从 BFF 加载:
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
<Tabs>
|
73
|
-
<TabItem value="macOS" label="macOS" default>
|
74
|
-
|
75
|
-
```bash
|
76
|
-
mkdir -p src/contacts/components/AllContacts/
|
77
|
-
mkdir -p src/contacts/components/ArchivedContacts/
|
78
|
-
touch src/contacts/components/AllContacts/index.tsx
|
79
|
-
touch src/contacts/components/ArchivedContacts/index.tsx
|
80
|
-
```
|
81
|
-
|
82
|
-
</TabItem>
|
83
|
-
<TabItem value="Windows" label="Windows">
|
84
|
-
|
85
|
-
```powershell
|
86
|
-
mkdir -p src/contacts/components/AllContacts/
|
87
|
-
mkdir -p src/contacts/components/ArchivedContacts/
|
88
|
-
ni src/contacts/components/AllContacts/index.tsx
|
89
|
-
ni src/contacts/components/ArchivedContacts/index.tsx
|
90
|
-
```
|
91
|
-
|
92
|
-
</TabItem>
|
93
|
-
</Tabs>
|
94
|
-
|
95
|
-
`ArchivedContacts/index.tsx` 的内容:
|
96
|
-
|
97
|
-
```js
|
70
|
+
```tsx
|
71
|
+
import { Helmet } from '@modern-js/runtime/head';
|
98
72
|
import { List } from 'antd';
|
99
|
-
import
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
avatar: `https://avatars.dicebear.com/v2/identicon/${user.name}.svg`,
|
105
|
-
}));
|
106
|
-
|
107
|
-
const mockArchivedData = getAvatar([
|
108
|
-
{ name: 'Thomas', email: 'w.kccip@bllmfbgv.dm' },
|
109
|
-
{ name: 'Chow', email: 'f.lfqljnlk@ywoefljhc.af' },
|
110
|
-
]);
|
111
|
-
|
112
|
-
const ArchivedContacts = () => (
|
113
|
-
<List
|
114
|
-
dataSource={mockArchivedData}
|
115
|
-
renderItem={info => <Item key={info.name} info={info} />}
|
116
|
-
/>
|
117
|
-
);
|
118
|
-
|
119
|
-
export default ArchivedContacts;
|
120
|
-
```
|
121
|
-
|
122
|
-
`AllContacts/index.tsx` 的内容:
|
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';
|
123
78
|
|
124
|
-
```tsx
|
125
79
|
import { useState, useEffect } from 'react';
|
126
|
-
import { List } from 'antd';
|
127
80
|
import { get as contacts } from '@api/contacts';
|
128
|
-
import Item from '
|
81
|
+
import Item from './components/Item';
|
129
82
|
|
130
|
-
|
83
|
+
function Index() {
|
131
84
|
const [list, setList] = useState(
|
132
85
|
[] as Array<{ name: string; email: string; avatar: string }>,
|
133
86
|
);
|
@@ -141,20 +94,25 @@ const AllContacts = () => {
|
|
141
94
|
}
|
142
95
|
});
|
143
96
|
return (
|
144
|
-
|
145
|
-
<
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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>
|
154
112
|
);
|
155
|
-
}
|
113
|
+
}
|
156
114
|
|
157
|
-
export default
|
115
|
+
export default Index;
|
158
116
|
```
|
159
117
|
|
160
118
|
在 Modern.js 中,可以通过调用函数(前后端一体化)的方式,直接调用 BFF 接口,调用时无需关心域名、路径等。
|
@@ -163,38 +121,6 @@ export default AllContacts;
|
|
163
121
|
在使用 `pnpm run new` 创建 BFF 时,Modern.js 已经默认在 `tsconig.json` 中注入了别名。这也是之前提到的,生成器在项目创建之后并不会被抛弃,仍旧可以在开发过程中不断为项目提供新的内容。
|
164
122
|
:::
|
165
123
|
|
166
|
-
接下来修改 `src/contacts/App.tsx`,把 `List` 组件的调用代码替换成上面的 `AllContacts` 和 `ArchivedContacts`:
|
167
|
-
|
168
|
-
```tsx
|
169
|
-
<Route path="/" exact={true}>
|
170
|
-
<Helmet>
|
171
|
-
<title>All</title>
|
172
|
-
</Helmet>
|
173
|
-
<AllContacts />
|
174
|
-
</Route>
|
175
|
-
<Route path="/archives" exact={true}>
|
176
|
-
<Helmet>
|
177
|
-
<title>Archives</title>
|
178
|
-
</Helmet>
|
179
|
-
<ArchivedContacts />
|
180
|
-
</Route>
|
181
|
-
```
|
182
|
-
|
183
|
-
删除 `App.tsx` 里的 mock 数据和 `List` 组件,把 `Item` 组件替换成 `ContactList`:
|
184
|
-
|
185
|
-
```tsx
|
186
|
-
import { useState } from 'react';
|
187
|
-
import { Radio, RadioChangeEvent } from 'antd';
|
188
|
-
import { Route, Switch, useHistory } from '@modern-js/runtime/router';
|
189
|
-
import { Helmet } from '@modern-js/runtime/head';
|
190
|
-
import 'tailwindcss/base.css';
|
191
|
-
import 'tailwindcss/components.css';
|
192
|
-
import 'tailwindcss/utilities.css';
|
193
|
-
import './styles/utils.css';
|
194
|
-
import AllContacts from './components/AllContacts';
|
195
|
-
import ArchivedContacts from './components/ArchivedContacts';
|
196
|
-
```
|
197
|
-
|
198
124
|
执行 `pnpm run dev`,再打开页面`http://localhost:8000/contacts`,可以看到页面加载后,会先初始化联系人数据(显示 pending),之后每次切换到 All 列表,也会重新请求联系人数据:
|
199
125
|
|
200
126
|

|
@@ -2,11 +2,11 @@
|
|
2
2
|
title: 应用架构
|
3
3
|
---
|
4
4
|
|
5
|
-
上一章节中,我们把硬编码的 `mockData` 改成从 BFF
|
5
|
+
上一章节中,我们把硬编码的 `mockData` 改成从 BFF 加载,在 `pages.tsx` 组件里用 BFF 函数,获取到联系人数据之后,保存在 `Index` 的组件内部状态里,而 `archives/index` 组件暂时继续使用 mock 数据。
|
6
6
|
|
7
|
-
|
7
|
+
本章节中,为了进一步实现项目功能,我们需要让两个组件都从 BFF 获取数据,还要实现【 Archive 】按钮,点击按钮能把联系人归档。
|
8
8
|
|
9
|
-
|
9
|
+
业务逻辑变复杂之后,相关代码不可避免会变多,如果都写在组件里,都会让这个组件的可读性和可维护性变差,让做不同事情的代码混在一起。
|
10
10
|
|
11
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
12
|
|
@@ -16,8 +16,6 @@ title: 应用架构
|
|
16
16
|
|
17
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
18
|
|
19
|
-
之前介绍的 `App.tsx` 和 `pages/` 都是其中一种代码模块,通过在最顶层对客户端路由的管理,把其他代码模块最终组织到一起,形成应用。
|
20
|
-
|
21
19
|
当前项目里 `components/` 目录中的 React 组件,是一种可以称作【 视图组件 】的代码模块,负责 UI、交互上的界面展现。
|
22
20
|
|
23
|
-
本章节我们来把 `
|
21
|
+
本章节我们来把 `page.tsx` 中的 Index 组件中可以跟 UI 解耦的业务逻辑,移到一种叫【 Model(业务模型)】的代码模块里。
|
@@ -121,11 +121,11 @@ const actions = {
|
|
121
121
|
为了做到高内聚低耦合,一个 Model 的 state、action、side effect 不应该分散在不同文件里。接下来我们把上面的代码连起来,放在同一个 Model 文件里,执行以下命令:
|
122
122
|
|
123
123
|
```bash
|
124
|
-
mkdir -p src/contacts/models/
|
125
|
-
touch src/contacts/models/contacts.ts
|
124
|
+
mkdir -p src/contacts/routes/models/
|
125
|
+
touch src/contacts/routes/models/contacts.ts
|
126
126
|
```
|
127
127
|
|
128
|
-
`src/contacts/models/contacts.ts` 的内容:
|
128
|
+
`src/contacts/routes/models/contacts.ts` 的内容:
|
129
129
|
|
130
130
|
```tsx
|
131
131
|
import { model } from '@modern-js/runtime/model';
|
@@ -2,18 +2,17 @@
|
|
2
2
|
title: 使用 Model
|
3
3
|
---
|
4
4
|
|
5
|
-
上一小节中,我们实现了 Model,已经把
|
5
|
+
上一小节中,我们实现了 Model,已经把 `page.tsx` `Index` 组件中原有的 UI 无关业务逻辑解耦出来。
|
6
6
|
|
7
|
-
这一小节中,我们使用这个 Model 直接把
|
7
|
+
这一小节中,我们使用这个 Model 直接把 `page.tsx` `Index` 组件重新还原出来,实现变得更简洁清晰:
|
8
8
|
|
9
|
-
```js title="src/contacts/
|
9
|
+
```js title="src/contacts/routes/page.tsx"
|
10
10
|
import { useEffect } from 'react';
|
11
11
|
import { useLocalModel } from '@modern-js/runtime/model';
|
12
|
-
import
|
13
|
-
import Item from '
|
14
|
-
import contacts from '../../models/contacts';
|
12
|
+
import contacts from './models/contacts';
|
13
|
+
import Item from './components/Item';
|
15
14
|
|
16
|
-
|
15
|
+
function Index() {
|
17
16
|
const [{ items, error, pending }, actions] = useLocalModel(contacts);
|
18
17
|
|
19
18
|
useEffect(() => {
|
@@ -22,23 +21,25 @@ const AllContacts = () => {
|
|
22
21
|
}
|
23
22
|
});
|
24
23
|
return (
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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>
|
35
36
|
);
|
36
|
-
}
|
37
|
+
}
|
37
38
|
|
38
|
-
export default
|
39
|
+
export default Index;
|
39
40
|
```
|
40
41
|
|
41
|
-
以上代码跟上一章节中 `
|
42
|
+
以上代码跟上一章节中 `Index` 组件的代码等价。
|
42
43
|
|
43
44
|
`useLocalModel` 是 Modern.js 提供的 hooks API,可以使用 Model,在组件中提供 Model 中定义的 state,或通过 actions 调用 Model 中定义的 side effect 与 action,从而改变 Model 的 state。
|
44
45
|
|
@@ -17,14 +17,14 @@ import TabItem from '@theme/TabItem';
|
|
17
17
|
<TabItem value="macOS" label="macOS" default>
|
18
18
|
|
19
19
|
```bash
|
20
|
-
touch src/contacts/models/contacts.test.ts
|
20
|
+
touch src/contacts/routes/models/contacts.test.ts
|
21
21
|
```
|
22
22
|
|
23
23
|
</TabItem>
|
24
24
|
<TabItem value="Windows" label="Windows">
|
25
25
|
|
26
26
|
```powershell
|
27
|
-
ni src/contacts/models/contacts.test.ts
|
27
|
+
ni src/contacts/routes/models/contacts.test.ts
|
28
28
|
```
|
29
29
|
|
30
30
|
</TabItem>
|