429 lines
11 KiB
Markdown
429 lines
11 KiB
Markdown
# 💡 使用示例
|
||
|
||
本文档提供项目中各个工具和模块的实际使用示例。
|
||
|
||
## 📋 目录
|
||
|
||
- [登录功能示例](#登录功能示例)
|
||
- [用户资料更新示例](#用户资料更新示例)
|
||
- [搜索功能示例](#搜索功能示例)
|
||
- [设置页面示例](#设置页面示例)
|
||
- [列表加载示例](#列表加载示例)
|
||
|
||
---
|
||
|
||
## 登录功能示例
|
||
|
||
完整的登录页面实现,包含表单验证、API 调用、状态管理。
|
||
|
||
```typescript
|
||
import React, { useState } from 'react';
|
||
import { View, TextInput, TouchableOpacity, Text, Alert } from 'react-native';
|
||
import { useForm, Controller } from 'react-hook-form';
|
||
import { zodResolver } from '@hookform/resolvers/zod';
|
||
import { useRouter } from 'expo-router';
|
||
import {
|
||
loginSchema,
|
||
type LoginFormData,
|
||
authService,
|
||
useUserStore,
|
||
useHaptics,
|
||
} from '@/src';
|
||
|
||
export default function LoginScreen() {
|
||
const router = useRouter();
|
||
const haptics = useHaptics();
|
||
const login = useUserStore((state) => state.login);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
// 表单配置
|
||
const {
|
||
control,
|
||
handleSubmit,
|
||
formState: { errors },
|
||
} = useForm<LoginFormData>({
|
||
resolver: zodResolver(loginSchema),
|
||
defaultValues: {
|
||
email: '',
|
||
password: '',
|
||
},
|
||
});
|
||
|
||
// 提交处理
|
||
const onSubmit = async (data: LoginFormData) => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
// 调用登录 API
|
||
const { user, token } = await authService.login(data);
|
||
|
||
// 更新状态
|
||
login(user, token);
|
||
|
||
// 触觉反馈
|
||
haptics.success();
|
||
|
||
// 跳转到首页
|
||
router.replace('/(tabs)');
|
||
} catch (error: any) {
|
||
haptics.error();
|
||
Alert.alert('登录失败', error.message || '请检查邮箱和密码');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<View style={{ padding: 20 }}>
|
||
{/* 邮箱输入 */}
|
||
<Controller
|
||
control={control}
|
||
name="email"
|
||
render={({ field: { onChange, value } }) => (
|
||
<View>
|
||
<TextInput
|
||
value={value}
|
||
onChangeText={onChange}
|
||
placeholder="邮箱"
|
||
keyboardType="email-address"
|
||
autoCapitalize="none"
|
||
style={{
|
||
borderWidth: 1,
|
||
borderColor: errors.email ? 'red' : '#ccc',
|
||
padding: 10,
|
||
borderRadius: 5,
|
||
}}
|
||
/>
|
||
{errors.email && (
|
||
<Text style={{ color: 'red', marginTop: 5 }}>
|
||
{errors.email.message}
|
||
</Text>
|
||
)}
|
||
</View>
|
||
)}
|
||
/>
|
||
|
||
{/* 密码输入 */}
|
||
<Controller
|
||
control={control}
|
||
name="password"
|
||
render={({ field: { onChange, value } }) => (
|
||
<View style={{ marginTop: 15 }}>
|
||
<TextInput
|
||
value={value}
|
||
onChangeText={onChange}
|
||
placeholder="密码"
|
||
secureTextEntry
|
||
style={{
|
||
borderWidth: 1,
|
||
borderColor: errors.password ? 'red' : '#ccc',
|
||
padding: 10,
|
||
borderRadius: 5,
|
||
}}
|
||
/>
|
||
{errors.password && (
|
||
<Text style={{ color: 'red', marginTop: 5 }}>
|
||
{errors.password.message}
|
||
</Text>
|
||
)}
|
||
</View>
|
||
)}
|
||
/>
|
||
|
||
{/* 登录按钮 */}
|
||
<TouchableOpacity
|
||
onPress={handleSubmit(onSubmit)}
|
||
disabled={loading}
|
||
style={{
|
||
backgroundColor: loading ? '#ccc' : '#007AFF',
|
||
padding: 15,
|
||
borderRadius: 5,
|
||
marginTop: 20,
|
||
}}
|
||
>
|
||
<Text style={{ color: 'white', textAlign: 'center', fontWeight: 'bold' }}>
|
||
{loading ? '登录中...' : '登录'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 用户资料更新示例
|
||
|
||
使用表单验证和 API 服务更新用户资料。
|
||
|
||
```typescript
|
||
import React, { useState } from 'react';
|
||
import { View, TextInput, TouchableOpacity, Text, Alert } from 'react-native';
|
||
import { useForm, Controller } from 'react-hook-form';
|
||
import { zodResolver } from '@hookform/resolvers/zod';
|
||
import {
|
||
updateProfileSchema,
|
||
type UpdateProfileFormData,
|
||
userService,
|
||
useUserStore,
|
||
useHaptics,
|
||
} from '@/src';
|
||
|
||
export default function EditProfileScreen() {
|
||
const haptics = useHaptics();
|
||
const user = useUserStore((state) => state.user);
|
||
const updateUser = useUserStore((state) => state.updateUser);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const {
|
||
control,
|
||
handleSubmit,
|
||
formState: { errors },
|
||
} = useForm<UpdateProfileFormData>({
|
||
resolver: zodResolver(updateProfileSchema),
|
||
defaultValues: {
|
||
nickname: user?.nickname || '',
|
||
phone: user?.phone || '',
|
||
},
|
||
});
|
||
|
||
const onSubmit = async (data: UpdateProfileFormData) => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
// 调用更新 API
|
||
const updatedUser = await userService.updateProfile(data);
|
||
|
||
// 更新本地状态
|
||
updateUser(updatedUser);
|
||
|
||
haptics.success();
|
||
Alert.alert('成功', '资料更新成功');
|
||
} catch (error: any) {
|
||
haptics.error();
|
||
Alert.alert('失败', error.message || '更新失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<View style={{ padding: 20 }}>
|
||
<Controller
|
||
control={control}
|
||
name="nickname"
|
||
render={({ field: { onChange, value } }) => (
|
||
<View>
|
||
<Text>昵称</Text>
|
||
<TextInput
|
||
value={value}
|
||
onChangeText={onChange}
|
||
placeholder="请输入昵称"
|
||
style={{ borderWidth: 1, padding: 10, marginTop: 5 }}
|
||
/>
|
||
{errors.nickname && (
|
||
<Text style={{ color: 'red' }}>{errors.nickname.message}</Text>
|
||
)}
|
||
</View>
|
||
)}
|
||
/>
|
||
|
||
<TouchableOpacity
|
||
onPress={handleSubmit(onSubmit)}
|
||
disabled={loading}
|
||
style={{
|
||
backgroundColor: '#007AFF',
|
||
padding: 15,
|
||
marginTop: 20,
|
||
borderRadius: 5,
|
||
}}
|
||
>
|
||
<Text style={{ color: 'white', textAlign: 'center' }}>
|
||
{loading ? '保存中...' : '保存'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 搜索功能示例
|
||
|
||
使用防抖优化搜索性能。
|
||
|
||
```typescript
|
||
import React, { useState, useEffect } from 'react';
|
||
import { View, TextInput, FlatList, Text } from 'react-native';
|
||
import { useDebounce } from '@/src';
|
||
|
||
export default function SearchScreen() {
|
||
const [searchText, setSearchText] = useState('');
|
||
const [results, setResults] = useState<any[]>([]);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
// 防抖搜索函数
|
||
const debouncedSearch = useDebounce(async (text: string) => {
|
||
if (!text.trim()) {
|
||
setResults([]);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
// 调用搜索 API
|
||
const response = await fetch(`/api/search?q=${text}`);
|
||
const data = await response.json();
|
||
setResults(data.results);
|
||
} catch (error) {
|
||
console.error('Search error:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, 500);
|
||
|
||
// 监听搜索文本变化
|
||
useEffect(() => {
|
||
debouncedSearch(searchText);
|
||
}, [searchText]);
|
||
|
||
return (
|
||
<View style={{ flex: 1, padding: 20 }}>
|
||
<TextInput
|
||
value={searchText}
|
||
onChangeText={setSearchText}
|
||
placeholder="搜索..."
|
||
style={{
|
||
borderWidth: 1,
|
||
borderColor: '#ccc',
|
||
padding: 10,
|
||
borderRadius: 5,
|
||
}}
|
||
/>
|
||
|
||
{loading && <Text style={{ marginTop: 10 }}>搜索中...</Text>}
|
||
|
||
<FlatList
|
||
data={results}
|
||
keyExtractor={(item) => item.id}
|
||
renderItem={({ item }) => (
|
||
<View style={{ padding: 10, borderBottomWidth: 1 }}>
|
||
<Text>{item.title}</Text>
|
||
</View>
|
||
)}
|
||
style={{ marginTop: 20 }}
|
||
/>
|
||
</View>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 设置页面示例
|
||
|
||
使用状态管理和触觉反馈。
|
||
|
||
```typescript
|
||
import React from 'react';
|
||
import { View, Text, Switch, TouchableOpacity } from 'react-native';
|
||
import {
|
||
useSettingsStore,
|
||
useHaptics,
|
||
type Theme,
|
||
} from '@/src';
|
||
|
||
export default function SettingsScreen() {
|
||
const haptics = useHaptics();
|
||
const theme = useSettingsStore((state) => state.theme);
|
||
const notificationsEnabled = useSettingsStore((state) => state.notificationsEnabled);
|
||
const hapticsEnabled = useSettingsStore((state) => state.hapticsEnabled);
|
||
|
||
const setTheme = useSettingsStore((state) => state.setTheme);
|
||
const setNotificationsEnabled = useSettingsStore((state) => state.setNotificationsEnabled);
|
||
const setHapticsEnabled = useSettingsStore((state) => state.setHapticsEnabled);
|
||
|
||
const handleThemeChange = (newTheme: Theme) => {
|
||
haptics.selection();
|
||
setTheme(newTheme);
|
||
};
|
||
|
||
const handleToggle = (setter: (value: boolean) => void, value: boolean) => {
|
||
haptics.light();
|
||
setter(value);
|
||
};
|
||
|
||
return (
|
||
<View style={{ flex: 1, padding: 20 }}>
|
||
{/* 主题选择 */}
|
||
<View style={{ marginBottom: 20 }}>
|
||
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>
|
||
主题
|
||
</Text>
|
||
{(['light', 'dark', 'auto'] as Theme[]).map((t) => (
|
||
<TouchableOpacity
|
||
key={t}
|
||
onPress={() => handleThemeChange(t)}
|
||
style={{
|
||
padding: 15,
|
||
backgroundColor: theme === t ? '#007AFF' : '#f0f0f0',
|
||
marginBottom: 10,
|
||
borderRadius: 5,
|
||
}}
|
||
>
|
||
<Text style={{ color: theme === t ? 'white' : 'black' }}>
|
||
{t === 'light' ? '亮色' : t === 'dark' ? '暗色' : '自动'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
))}
|
||
</View>
|
||
|
||
{/* 通知开关 */}
|
||
<View
|
||
style={{
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
marginBottom: 15,
|
||
}}
|
||
>
|
||
<Text>通知</Text>
|
||
<Switch
|
||
value={notificationsEnabled}
|
||
onValueChange={(value) => handleToggle(setNotificationsEnabled, value)}
|
||
/>
|
||
</View>
|
||
|
||
{/* 触觉反馈开关 */}
|
||
<View
|
||
style={{
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
}}
|
||
>
|
||
<Text>触觉反馈</Text>
|
||
<Switch
|
||
value={hapticsEnabled}
|
||
onValueChange={(value) => handleToggle(setHapticsEnabled, value)}
|
||
/>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📚 更多示例
|
||
|
||
查看以下文档了解更多:
|
||
|
||
- [工具库使用指南](./LIBRARIES.md) - 各个工具库的详细用法
|
||
- [项目结构说明](./PROJECT_STRUCTURE.md) - 项目结构和最佳实践
|
||
|
||
---
|
||
|
||
**提示**:这些示例都是可以直接使用的代码,复制到你的项目中即可!
|