@doubao-apps/ai 0.0.33 → 0.0.34
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/README.md +1 -32
- package/dist/733.js +7 -15
- package/dist/cli.js +1 -1
- package/dist/manifest.json +3 -11
- package/dist/skills/doubao-apps-dev/SKILL.md +103 -227
- package/dist/skills/doubao-apps-dev/references/examples/common-patterns.md +80 -66
- package/dist/skills/doubao-apps-dev/references/examples/component-basics.md +24 -3
- package/dist/skills/doubao-apps-dev/references/examples/component-recipes.md +71 -0
- package/dist/skills/doubao-apps-dev/references/examples/open-api-recipes.md +203 -0
- package/dist/skills/doubao-apps-dev/references/guides/component-development.md +66 -42
- package/dist/skills/doubao-apps-dev/references/guides/expired-widget.md +113 -0
- package/dist/skills/doubao-apps-dev/references/guides/mcp-ui.md +275 -0
- package/dist/skills/doubao-apps-dev/references/guides/performance-optimization.md +24 -7
- package/dist/skills/doubao-apps-dev/references/reference/components-quick-ref.md +18 -7
- package/dist/skills/doubao-apps-dev/references/reference/framework-api-quick-ref.md +125 -107
- package/dist/skills/doubao-apps-dev/references/reference/open-api/01-/345/237/272/347/241/200.md +136 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/02-/347/263/273/347/273/237.md +257 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/03-/345/256/232/344/275/215.md +326 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/04-/345/255/230/345/202/250.md +284 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/05-/350/267/257/347/224/261.md +162 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/06-/344/272/244/344/272/222.md +290 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/07-/350/276/223/345/205/245.md +63 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/08-/347/275/221/347/273/234.md +124 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/09-/345/252/222/344/275/223.md +192 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/10-/344/270/232/345/212/241/350/203/275/345/212/233.md +431 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/11-/347/231/273/345/275/225.md +110 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/12-/346/224/257/344/273/230.md +224 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/13-/346/225/260/346/215/256/345/210/206/346/236/220.md +43 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/14-/350/223/235/347/211/231.md +598 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/15-wi-fi.md +210 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/16-/345/212/240/351/200/237/345/272/246/350/256/241.md +87 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/17-ibeacon.md +97 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/18-/347/275/227/347/233/230.md +78 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/19-/350/256/276/345/244/207/346/226/271/345/220/221.md +86 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/20-/351/231/200/350/236/272/344/273/252.md +87 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/21-/350/256/276/345/244/207-/347/275/221/347/273/234.md +102 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/22-/347/237/255/344/277/241.md +40 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/23-/346/227/240/351/232/234/347/242/215.md +40 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/24-/345/237/272/347/241/200/344/277/241/346/201/257.md +86 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/25-/347/224/265/346/261/240.md +63 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/26-/346/227/245/345/216/206.md +95 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/27-/345/211/252/350/264/264/346/235/277.md +64 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/28-/350/201/224/347/263/273/344/272/272.md +101 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/29-/345/212/240/345/257/206.md +43 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/30-/347/224/265/350/257/235.md +36 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/31-/346/211/253/347/240/201.md +58 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/32-/345/261/217/345/271/225.md +136 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/33-/351/234/207/345/212/250.md +62 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/34-/346/226/207/344/273/266/347/263/273/347/273/237.md +73 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/README.md +34 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api.md +384 -2
- package/dist/skills/doubao-apps-dev/references/rules/dos-and-donts.md +44 -27
- package/dist/skills/h5-to-doubao/SKILL.md +8 -4
- package/dist/skills/weixin-to-doubao/SKILL.md +8 -4
- package/dist/skills/weixin-to-doubao/assets/migration-checklist-template.md +1 -1
- package/package.json +2 -3
- package/dist/contexts/doubao-apps-dev/AGENTS.md +0 -300
- package/dist/contexts/doubao-apps-dev/CLAUDE.md +0 -31
- package/dist/contexts/doubao-apps-dev/references/examples/common-patterns.md +0 -589
- package/dist/contexts/doubao-apps-dev/references/examples/component-basics.md +0 -526
- package/dist/contexts/doubao-apps-dev/references/guides/best-practices.md +0 -571
- package/dist/contexts/doubao-apps-dev/references/guides/component-development.md +0 -891
- package/dist/contexts/doubao-apps-dev/references/guides/performance-optimization.md +0 -402
- package/dist/contexts/doubao-apps-dev/references/guides/troubleshooting.md +0 -287
- package/dist/contexts/doubao-apps-dev/references/reference/components-quick-ref.md +0 -103
- package/dist/contexts/doubao-apps-dev/references/reference/framework-api-quick-ref.md +0 -550
- package/dist/contexts/doubao-apps-dev/references/reference/open-api/README.md +0 -8
- package/dist/contexts/doubao-apps-dev/references/reference/open-api.md +0 -11
- package/dist/contexts/doubao-apps-dev/references/rules/dos-and-donts.md +0 -467
|
@@ -1,571 +0,0 @@
|
|
|
1
|
-
# 最佳实践
|
|
2
|
-
|
|
3
|
-
Doubao Apps SDK 框架的最佳开发实践和代码规范。
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 🎯 核心原则
|
|
8
|
-
|
|
9
|
-
### SOLID 原则
|
|
10
|
-
|
|
11
|
-
1. **单一职责原则**(Single Responsibility)
|
|
12
|
-
- 每个组件只负责一个功能
|
|
13
|
-
- 函数保持简洁,只做一件事
|
|
14
|
-
|
|
15
|
-
2. **开闭原则**(Open/Closed)
|
|
16
|
-
- 对扩展开放,对修改关闭
|
|
17
|
-
- 使用组合而非修改现有代码
|
|
18
|
-
|
|
19
|
-
3. **依赖倒置原则**(Dependency Inversion)
|
|
20
|
-
- 依赖抽象而非具体实现
|
|
21
|
-
- 通过 props 传递依赖
|
|
22
|
-
|
|
23
|
-
### DRY 原则
|
|
24
|
-
|
|
25
|
-
**Don't Repeat Yourself** - 避免重复代码
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
// ✅ 好的做法 - 抽取公共逻辑
|
|
29
|
-
const useUserData = () => {
|
|
30
|
-
const [user, setUser] = useState(null);
|
|
31
|
-
const [loading, setLoading] = useState(false);
|
|
32
|
-
|
|
33
|
-
const fetchUser = async () => {
|
|
34
|
-
setLoading(true);
|
|
35
|
-
try {
|
|
36
|
-
const data = await api.getUser();
|
|
37
|
-
setUser(data);
|
|
38
|
-
} finally {
|
|
39
|
-
setLoading(false);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
return { user, loading, fetchUser };
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// 在多个组件中复用
|
|
47
|
-
function ProfilePage() {
|
|
48
|
-
const { user, loading } = useUserData();
|
|
49
|
-
// ...
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
```tsx
|
|
54
|
-
// ❌ 不好的做法 - 重复代码
|
|
55
|
-
function ProfilePage() {
|
|
56
|
-
const [user, setUser] = useState(null);
|
|
57
|
-
const [loading, setLoading] = useState(false);
|
|
58
|
-
|
|
59
|
-
const fetchUser = async () => {
|
|
60
|
-
setLoading(true);
|
|
61
|
-
try {
|
|
62
|
-
const data = await api.getUser();
|
|
63
|
-
setUser(data);
|
|
64
|
-
} finally {
|
|
65
|
-
setLoading(false);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
// ...
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function SettingsPage() {
|
|
72
|
-
// 完全相同的代码又写一遍
|
|
73
|
-
const [user, setUser] = useState(null);
|
|
74
|
-
// ...
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## 📂 项目组织
|
|
81
|
-
|
|
82
|
-
### 目录结构
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
src/
|
|
86
|
-
├── pages/ # 页面组件
|
|
87
|
-
│ └── home/
|
|
88
|
-
│ ├── index.tsx
|
|
89
|
-
│ ├── index.scss
|
|
90
|
-
│ └── components/ # 页面私有组件
|
|
91
|
-
│ └── Header.tsx
|
|
92
|
-
├── widgets/ # Widget 组件
|
|
93
|
-
│ └── weather-card/
|
|
94
|
-
│ ├── index.tsx
|
|
95
|
-
│ └── index.scss
|
|
96
|
-
├── components/ # 公共组件
|
|
97
|
-
│ ├── Button/
|
|
98
|
-
│ │ ├── index.tsx
|
|
99
|
-
│ │ └── index.scss
|
|
100
|
-
│ └── Loading/
|
|
101
|
-
│ ├── index.tsx
|
|
102
|
-
│ └── index.scss
|
|
103
|
-
├── hooks/ # 自定义 Hooks
|
|
104
|
-
│ ├── useAuth.ts
|
|
105
|
-
│ └── useData.ts
|
|
106
|
-
├── utils/ # 工具函数
|
|
107
|
-
│ ├── format.ts
|
|
108
|
-
│ └── validate.ts
|
|
109
|
-
├── constants/ # 常量定义
|
|
110
|
-
│ └── index.ts
|
|
111
|
-
├── types/ # 类型定义
|
|
112
|
-
│ └── index.ts
|
|
113
|
-
└── app.ts # 应用入口
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### 文件命名规范
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
✅ 好的命名:
|
|
120
|
-
- UserProfile.tsx (组件:PascalCase)
|
|
121
|
-
- useAuth.ts (Hook:camelCase with use prefix)
|
|
122
|
-
- formatDate.ts (工具函数:camelCase)
|
|
123
|
-
- API_BASE_URL.ts (常量:UPPER_SNAKE_CASE)
|
|
124
|
-
|
|
125
|
-
❌ 不好的命名:
|
|
126
|
-
- userprofile.tsx
|
|
127
|
-
- UseAuth.ts
|
|
128
|
-
- Format-Date.ts
|
|
129
|
-
- apibaseurl.ts
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 最佳实践
|
|
135
|
-
|
|
136
|
-
### 组件设计
|
|
137
|
-
|
|
138
|
-
#### 保持组件简洁
|
|
139
|
-
|
|
140
|
-
```tsx
|
|
141
|
-
// ✅ 好的做法 - 单一职责
|
|
142
|
-
function UserAvatar({ url, size = 'medium' }: Props) {
|
|
143
|
-
return (
|
|
144
|
-
<image
|
|
145
|
-
className={`user-avatar user-avatar--${size}`}
|
|
146
|
-
src={url}
|
|
147
|
-
/>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function UserCard({ user }: Props) {
|
|
152
|
-
return (
|
|
153
|
-
<view className="user-card">
|
|
154
|
-
<UserAvatar url={user.avatar} size="large" />
|
|
155
|
-
<text>{user.name}</text>
|
|
156
|
-
</view>
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
```tsx
|
|
162
|
-
// ❌ 不好的做法 - 组件过于复杂
|
|
163
|
-
function UserCard({ user }: Props) {
|
|
164
|
-
return (
|
|
165
|
-
<view className="user-card">
|
|
166
|
-
{/* 太多内联逻辑 */}
|
|
167
|
-
<image
|
|
168
|
-
src={user.avatar}
|
|
169
|
-
style={{
|
|
170
|
-
width: user.isVip ? '120rpx' : '80rpx',
|
|
171
|
-
height: user.isVip ? '120rpx' : '80rpx',
|
|
172
|
-
borderRadius: user.isVip ? '60rpx' : '40rpx',
|
|
173
|
-
border: user.isVip ? '2px solid gold' : 'none'
|
|
174
|
-
}}
|
|
175
|
-
/>
|
|
176
|
-
<text>{user.name}</text>
|
|
177
|
-
{user.isVip && <text>VIP</text>}
|
|
178
|
-
{/* 更多复杂逻辑... */}
|
|
179
|
-
</view>
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
#### 使用 TypeScript 类型
|
|
185
|
-
|
|
186
|
-
```tsx
|
|
187
|
-
// ✅ 好的做法 - 完整的类型定义
|
|
188
|
-
interface User {
|
|
189
|
-
id: string;
|
|
190
|
-
name: string;
|
|
191
|
-
email: string;
|
|
192
|
-
avatar?: string;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
interface UserCardProps {
|
|
196
|
-
user: User;
|
|
197
|
-
onPress?: (userId: string) => void;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function UserCard({ user, onPress }: UserCardProps) {
|
|
201
|
-
return (
|
|
202
|
-
<view onClick={() => onPress?.(user.id)}>
|
|
203
|
-
<text>{user.name}</text>
|
|
204
|
-
</view>
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Hooks 使用
|
|
210
|
-
|
|
211
|
-
#### 自定义 Hooks
|
|
212
|
-
|
|
213
|
-
```tsx
|
|
214
|
-
// ✅ 好的做法 - 封装复杂逻辑
|
|
215
|
-
function useDebounce<T>(value: T, delay: number): T {
|
|
216
|
-
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
217
|
-
|
|
218
|
-
useEffect(() => {
|
|
219
|
-
const handler = setTimeout(() => {
|
|
220
|
-
setDebouncedValue(value);
|
|
221
|
-
}, delay);
|
|
222
|
-
|
|
223
|
-
return () => clearTimeout(handler);
|
|
224
|
-
}, [value, delay]);
|
|
225
|
-
|
|
226
|
-
return debouncedValue;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// 使用
|
|
230
|
-
function SearchPage() {
|
|
231
|
-
const [query, setQuery] = useState('');
|
|
232
|
-
const debouncedQuery = useDebounce(query, 500);
|
|
233
|
-
|
|
234
|
-
useEffect(() => {
|
|
235
|
-
if (debouncedQuery) {
|
|
236
|
-
performSearch(debouncedQuery);
|
|
237
|
-
}
|
|
238
|
-
}, [debouncedQuery]);
|
|
239
|
-
|
|
240
|
-
return <input value={query} onInput={e => setQuery(e.detail.value)} />;
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
#### useMemo 和 useCallback
|
|
245
|
-
|
|
246
|
-
```tsx
|
|
247
|
-
// ✅ 好的做法 - 优化性能
|
|
248
|
-
function DataList({ items, filter }: Props) {
|
|
249
|
-
// 缓存计算结果
|
|
250
|
-
const filteredItems = useMemo(() => {
|
|
251
|
-
return items.filter(item => item.type === filter);
|
|
252
|
-
}, [items, filter]);
|
|
253
|
-
|
|
254
|
-
// 稳定函数引用
|
|
255
|
-
const handleItemClick = useCallback((id: string) => {
|
|
256
|
-
console.log('Clicked:', id);
|
|
257
|
-
}, []);
|
|
258
|
-
|
|
259
|
-
return (
|
|
260
|
-
<view>
|
|
261
|
-
{filteredItems.map(item => (
|
|
262
|
-
<ItemCard key={item.id} item={item} onClick={handleItemClick} />
|
|
263
|
-
))}
|
|
264
|
-
</view>
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
---
|
|
270
|
-
|
|
271
|
-
## 🎨 样式最佳实践
|
|
272
|
-
|
|
273
|
-
### SCSS 组织
|
|
274
|
-
|
|
275
|
-
```scss
|
|
276
|
-
// ✅ 好的做法 - 使用变量和嵌套
|
|
277
|
-
$primary-color: #1890ff;
|
|
278
|
-
$spacing-unit: 8rpx;
|
|
279
|
-
$border-radius: 8rpx;
|
|
280
|
-
|
|
281
|
-
.user-card {
|
|
282
|
-
padding: $spacing-unit * 2;
|
|
283
|
-
border-radius: $border-radius;
|
|
284
|
-
background: #ffffff;
|
|
285
|
-
|
|
286
|
-
&__header {
|
|
287
|
-
display: flex;
|
|
288
|
-
align-items: center;
|
|
289
|
-
margin-bottom: $spacing-unit * 3;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
&__title {
|
|
293
|
-
font-size: 32rpx;
|
|
294
|
-
color: $primary-color;
|
|
295
|
-
font-weight: bold;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
&--highlighted {
|
|
299
|
-
border: 2rpx solid $primary-color;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### BEM 命名规范
|
|
305
|
-
|
|
306
|
-
```scss
|
|
307
|
-
// Block__Element--Modifier
|
|
308
|
-
|
|
309
|
-
.card { } // Block
|
|
310
|
-
.card__header { } // Element
|
|
311
|
-
.card__body { } // Element
|
|
312
|
-
.card--featured { } // Modifier
|
|
313
|
-
.card__header--large { } // Element Modifier
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### 响应式设计
|
|
317
|
-
|
|
318
|
-
```scss
|
|
319
|
-
// ✅ 使用 rpx 单位
|
|
320
|
-
.container {
|
|
321
|
-
padding: 32rpx; // 响应式单位
|
|
322
|
-
font-size: 28rpx;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// ❌ 避免使用固定 px
|
|
326
|
-
.container {
|
|
327
|
-
padding: 16px; // 不会响应屏幕尺寸
|
|
328
|
-
font-size: 14px;
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
---
|
|
333
|
-
|
|
334
|
-
## 🚀 性能优化
|
|
335
|
-
|
|
336
|
-
### 避免不必要的渲染
|
|
337
|
-
|
|
338
|
-
```tsx
|
|
339
|
-
// ✅ 好的做法 - 使用 useMemo
|
|
340
|
-
function ParentComponent({ items }: Props) {
|
|
341
|
-
const processedData = useMemo(() => {
|
|
342
|
-
return items.map(item => ({
|
|
343
|
-
...item,
|
|
344
|
-
computed: expensiveCalculation(item)
|
|
345
|
-
}));
|
|
346
|
-
}, [items]);
|
|
347
|
-
|
|
348
|
-
return <ExpensiveComponent data={processedData} />;
|
|
349
|
-
}
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### 列表渲染优化
|
|
353
|
-
|
|
354
|
-
```tsx
|
|
355
|
-
// ✅ 好的做法 - 使用唯一稳定的 key
|
|
356
|
-
function ItemList({ items }: Props) {
|
|
357
|
-
return (
|
|
358
|
-
<list>
|
|
359
|
-
{items.map(item => (
|
|
360
|
-
<list-item key={item.id}> {/* 使用唯一 ID */}
|
|
361
|
-
<ItemCard item={item} />
|
|
362
|
-
</list-item>
|
|
363
|
-
))}
|
|
364
|
-
</list>
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// ❌ 不好的做法 - 使用 index 作为 key
|
|
369
|
-
function ItemList({ items }: Props) {
|
|
370
|
-
return (
|
|
371
|
-
<list>
|
|
372
|
-
{items.map((item, index) => (
|
|
373
|
-
<list-item key={index}> {/* 列表顺序改变会有问题 */}
|
|
374
|
-
<ItemCard item={item} />
|
|
375
|
-
</list-item>
|
|
376
|
-
))}
|
|
377
|
-
</list>
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
---
|
|
383
|
-
|
|
384
|
-
## 🔒 错误处理
|
|
385
|
-
|
|
386
|
-
### Try-Catch 使用
|
|
387
|
-
|
|
388
|
-
```tsx
|
|
389
|
-
// ✅ 好的做法 - 完整的错误处理
|
|
390
|
-
async function fetchData() {
|
|
391
|
-
try {
|
|
392
|
-
setLoading(true);
|
|
393
|
-
setError(null);
|
|
394
|
-
|
|
395
|
-
const response = await api.getData();
|
|
396
|
-
setData(response);
|
|
397
|
-
} catch (error) {
|
|
398
|
-
// 记录错误
|
|
399
|
-
console.error('Failed to fetch data:', error);
|
|
400
|
-
|
|
401
|
-
// 设置用户友好的错误信息
|
|
402
|
-
if (error.code === 'NETWORK_ERROR') {
|
|
403
|
-
setError('网络连接失败,请检查您的网络');
|
|
404
|
-
} else if (error.code === 'AUTH_ERROR') {
|
|
405
|
-
setError('您的登录已过期,请重新登录');
|
|
406
|
-
} else {
|
|
407
|
-
setError('数据加载失败,请稍后重试');
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// 可选:上报错误到监控系统
|
|
411
|
-
reportError(error);
|
|
412
|
-
} finally {
|
|
413
|
-
setLoading(false);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
### 边界情况处理
|
|
419
|
-
|
|
420
|
-
```tsx
|
|
421
|
-
// ✅ 好的做法 - 处理各种边界情况
|
|
422
|
-
function DataDisplay({ data }: { data?: Data[] }) {
|
|
423
|
-
// 处理 undefined
|
|
424
|
-
if (!data) {
|
|
425
|
-
return <LoadingView />;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// 处理空数组
|
|
429
|
-
if (data.length === 0) {
|
|
430
|
-
return <EmptyView message="暂无数据" />;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// 正常展示
|
|
434
|
-
return (
|
|
435
|
-
<view>
|
|
436
|
-
{data.map(item => (
|
|
437
|
-
<ItemCard key={item.id} item={item} />
|
|
438
|
-
))}
|
|
439
|
-
</view>
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
---
|
|
445
|
-
|
|
446
|
-
## 📱 用户体验
|
|
447
|
-
|
|
448
|
-
### 加载状态
|
|
449
|
-
|
|
450
|
-
```tsx
|
|
451
|
-
// ✅ 好的做法 - 提供清晰的加载反馈
|
|
452
|
-
function DataPage() {
|
|
453
|
-
const [data, setData] = useState(null);
|
|
454
|
-
const [loading, setLoading] = useState(true);
|
|
455
|
-
|
|
456
|
-
if (loading) {
|
|
457
|
-
return (
|
|
458
|
-
<view className="loading-container">
|
|
459
|
-
<loading-spinner />
|
|
460
|
-
<text>加载中...</text>
|
|
461
|
-
</view>
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return <DataView data={data} />;
|
|
466
|
-
}
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
### 错误反馈
|
|
470
|
-
|
|
471
|
-
```tsx
|
|
472
|
-
// ✅ 好的做法 - 提供可操作的错误提示
|
|
473
|
-
function ErrorView({ error, onRetry }: Props) {
|
|
474
|
-
return (
|
|
475
|
-
<view className="error-container">
|
|
476
|
-
<image src="/assets/error-icon.png" className="error-icon" />
|
|
477
|
-
<text className="error-message">{error}</text>
|
|
478
|
-
<button className="retry-button" onClick={onRetry}>
|
|
479
|
-
重试
|
|
480
|
-
</button>
|
|
481
|
-
</view>
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
### 交互反馈
|
|
487
|
-
|
|
488
|
-
```tsx
|
|
489
|
-
// ✅ 好的做法 - 提供即时反馈
|
|
490
|
-
function SubmitButton({ onSubmit }: Props) {
|
|
491
|
-
const [submitting, setSubmitting] = useState(false);
|
|
492
|
-
|
|
493
|
-
const handleSubmit = async () => {
|
|
494
|
-
setSubmitting(true);
|
|
495
|
-
try {
|
|
496
|
-
await onSubmit();
|
|
497
|
-
showToast({ message: '提交成功' });
|
|
498
|
-
} catch (error) {
|
|
499
|
-
showToast({ message: '提交失败,请重试' });
|
|
500
|
-
} finally {
|
|
501
|
-
setSubmitting(false);
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
return (
|
|
506
|
-
<button
|
|
507
|
-
className="submit-button"
|
|
508
|
-
onClick={handleSubmit}
|
|
509
|
-
disabled={submitting}
|
|
510
|
-
>
|
|
511
|
-
{submitting ? '提交中...' : '提交'}
|
|
512
|
-
</button>
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
---
|
|
518
|
-
|
|
519
|
-
## 📝 代码注释
|
|
520
|
-
|
|
521
|
-
### 何时添加注释
|
|
522
|
-
|
|
523
|
-
```tsx
|
|
524
|
-
// ✅ 好的做法 - 解释"为什么",而不是"是什么"
|
|
525
|
-
function calculateDiscount(price: number, userLevel: string): number {
|
|
526
|
-
// VIP 用户享受额外 10% 折扣(业务需求)
|
|
527
|
-
if (userLevel === 'VIP') {
|
|
528
|
-
return price * 0.9;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
return price;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// ❌ 不好的做法 - 注释重复代码
|
|
535
|
-
function calculateDiscount(price: number, userLevel: string): number {
|
|
536
|
-
// 如果用户等级是 VIP
|
|
537
|
-
if (userLevel === 'VIP') {
|
|
538
|
-
// 返回价格乘以 0.9
|
|
539
|
-
return price * 0.9;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// 返回原价
|
|
543
|
-
return price;
|
|
544
|
-
}
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
### JSDoc 注释
|
|
548
|
-
|
|
549
|
-
```tsx
|
|
550
|
-
/**
|
|
551
|
-
* 格式化日期为 YYYY-MM-DD 格式
|
|
552
|
-
* @param date - 要格式化的日期对象
|
|
553
|
-
* @param separator - 分隔符,默认为 '-'
|
|
554
|
-
* @returns 格式化后的日期字符串
|
|
555
|
-
* @example
|
|
556
|
-
* formatDate(new Date('2024-01-15')) // '2024-01-15'
|
|
557
|
-
* formatDate(new Date('2024-01-15'), '/') // '2024/01/15'
|
|
558
|
-
*/
|
|
559
|
-
function formatDate(date: Date, separator: string = '-'): string {
|
|
560
|
-
// 实现...
|
|
561
|
-
}
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
---
|
|
565
|
-
|
|
566
|
-
## 🔗 相关文档
|
|
567
|
-
|
|
568
|
-
- [开发规则](../rules/dos-and-donts.md)
|
|
569
|
-
- [组件开发完整指南](./component-development.md)
|
|
570
|
-
- [性能优化](./performance-optimization.md)
|
|
571
|
-
- [故障排查](./troubleshooting.md)
|