# 最佳实践

Doubao Apps SDK 框架的最佳开发实践和代码规范。

---

## 🎯 核心原则

### SOLID 原则

1. **单一职责原则**（Single Responsibility）
   - 每个组件只负责一个功能
   - 函数保持简洁，只做一件事

2. **开闭原则**（Open/Closed）
   - 对扩展开放，对修改关闭
   - 使用组合而非修改现有代码

3. **依赖倒置原则**（Dependency Inversion）
   - 依赖抽象而非具体实现
   - 通过 props 传递依赖

### DRY 原则

**Don't Repeat Yourself** - 避免重复代码

```tsx
// ✅ 好的做法 - 抽取公共逻辑
const useUserData = () => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);

  const fetchUser = async () => {
    setLoading(true);
    try {
      const data = await api.getUser();
      setUser(data);
    } finally {
      setLoading(false);
    }
  };

  return { user, loading, fetchUser };
};

// 在多个组件中复用
function ProfilePage() {
  const { user, loading } = useUserData();
  // ...
}
```

```tsx
// ❌ 不好的做法 - 重复代码
function ProfilePage() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);

  const fetchUser = async () => {
    setLoading(true);
    try {
      const data = await api.getUser();
      setUser(data);
    } finally {
      setLoading(false);
    }
  };
  // ...
}

function SettingsPage() {
  // 完全相同的代码又写一遍
  const [user, setUser] = useState(null);
  // ...
}
```

---

## 📂 项目组织

### 目录结构

```
src/
├── pages/           # 页面组件
│   └── home/
│       ├── index.tsx
│       ├── index.scss
│       └── components/  # 页面私有组件
│           └── Header.tsx
├── widgets/         # Widget 组件
│   └── weather-card/
│       ├── index.tsx
│       └── index.scss
├── components/      # 公共组件
│   ├── Button/
│   │   ├── index.tsx
│   │   └── index.scss
│   └── Loading/
│       ├── index.tsx
│       └── index.scss
├── hooks/           # 自定义 Hooks
│   ├── useAuth.ts
│   └── useData.ts
├── utils/           # 工具函数
│   ├── format.ts
│   └── validate.ts
├── constants/       # 常量定义
│   └── index.ts
├── types/           # 类型定义
│   └── index.ts
└── app.ts          # 应用入口
```

### 文件命名规范

```
✅ 好的命名：
- UserProfile.tsx        (组件：PascalCase)
- useAuth.ts            (Hook：camelCase with use prefix)
- formatDate.ts         (工具函数：camelCase)
- API_BASE_URL.ts       (常量：UPPER_SNAKE_CASE)

❌ 不好的命名：
- userprofile.tsx
- UseAuth.ts
- Format-Date.ts
- apibaseurl.ts
```

Widget 卡片整体结构优先使用 [Widget 模板库](../widget-templates/overview.md) 中的模板；下面的组件拆分示例适用于 Page，
不用于手写 Widget 卡片外壳。

---

##  最佳实践

### 组件设计

#### 保持组件简洁

```tsx
// ✅ 好的做法 - 单一职责
function UserAvatar({ url, size = 'medium' }: Props) {
  return (
    <image
      className={`user-avatar user-avatar--${size}`}
      src={url}
    />
  );
}

function UserCard({ user }: Props) {
  return (
    <view className="user-card">
      <UserAvatar url={user.avatar} size="large" />
      <text>{user.name}</text>
    </view>
  );
}
```

```tsx
// ❌ 不好的做法 - 组件过于复杂
function UserCard({ user }: Props) {
  return (
    <view className="user-card">
      {/* 太多内联逻辑 */}
      <image
        src={user.avatar}
        style={{
          width: user.isVip ? '120rpx' : '80rpx',
          height: user.isVip ? '120rpx' : '80rpx',
          borderRadius: user.isVip ? '60rpx' : '40rpx',
          border: user.isVip ? '2px solid gold' : 'none'
        }}
      />
      <text>{user.name}</text>
      {user.isVip && <text>VIP</text>}
      {/* 更多复杂逻辑... */}
    </view>
  );
}
```

#### 使用 TypeScript 类型

```tsx
// ✅ 好的做法 - 完整的类型定义
interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
}

interface UserCardProps {
  user: User;
  onPress?: (userId: string) => void;
}

function UserCard({ user, onPress }: UserCardProps) {
  return (
    <view onClick={() => onPress?.(user.id)}>
      <text>{user.name}</text>
    </view>
  );
}
```

### Hooks 使用

#### 自定义 Hooks

```tsx
// ✅ 好的做法 - 封装复杂逻辑
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

// 使用
function SearchPage() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);

  useEffect(() => {
    if (debouncedQuery) {
      performSearch(debouncedQuery);
    }
  }, [debouncedQuery]);

  return <input value={query} onInput={e => setQuery(e.detail.value)} />;
}
```

#### useMemo 和 useCallback

```tsx
// ✅ 好的做法 - 优化性能
function DataList({ items, filter }: Props) {
  // 缓存计算结果
  const filteredItems = useMemo(() => {
    return items.filter(item => item.type === filter);
  }, [items, filter]);

  // 稳定函数引用
  const handleItemClick = useCallback((id: string) => {
    console.log('Clicked:', id);
  }, []);

  return (
    <view>
      {filteredItems.map(item => (
        <ItemCard key={item.id} item={item} onClick={handleItemClick} />
      ))}
    </view>
  );
}
```

---

## 🎨 样式最佳实践

### SCSS 组织

```scss
// ✅ 好的做法 - 使用变量和嵌套
$primary-color: #1890ff;
$spacing-unit: 8rpx;
$border-radius: 8rpx;

.user-card {
  padding: $spacing-unit * 2;
  border-radius: $border-radius;
  background: #ffffff;

  &__header {
    display: flex;
    align-items: center;
    margin-bottom: $spacing-unit * 3;
  }

  &__title {
    font-size: 32rpx;
    color: $primary-color;
    font-weight: bold;
  }

  &--highlighted {
    border: 2rpx solid $primary-color;
  }
}
```

### BEM 命名规范

```scss
// Block__Element--Modifier

.card { }                    // Block
.card__header { }            // Element
.card__body { }              // Element
.card--featured { }          // Modifier
.card__header--large { }     // Element Modifier
```

### 响应式设计

```scss
// ✅ 使用 rpx 单位
.container {
  padding: 32rpx;           // 响应式单位
  font-size: 28rpx;
}

// ❌ 避免使用固定 px
.container {
  padding: 16px;            // 不会响应屏幕尺寸
  font-size: 14px;
}
```

---

## 🚀 性能优化

### 避免不必要的渲染

```tsx
// ✅ 好的做法 - 使用 useMemo
function ParentComponent({ items }: Props) {
  const processedData = useMemo(() => {
    return items.map(item => ({
      ...item,
      computed: expensiveCalculation(item)
    }));
  }, [items]);

  return <ExpensiveComponent data={processedData} />;
}
```

### 列表渲染优化

```tsx
// ✅ 好的做法 - 使用唯一稳定的 key
function ItemList({ items }: Props) {
  return (
    <list>
      {items.map(item => (
        <list-item key={item.id}>  {/* 使用唯一 ID */}
          <ItemCard item={item} />
        </list-item>
      ))}
    </list>
  );
}

// ❌ 不好的做法 - 使用 index 作为 key
function ItemList({ items }: Props) {
  return (
    <list>
      {items.map((item, index) => (
        <list-item key={index}>  {/* 列表顺序改变会有问题 */}
          <ItemCard item={item} />
        </list-item>
      ))}
    </list>
  );
}
```

---

## 🔒 错误处理

### Try-Catch 使用

```tsx
// ✅ 好的做法 - 完整的错误处理
async function fetchData() {
  try {
    setLoading(true);
    setError(null);

    const response = await api.getData();
    setData(response);
  } catch (error) {
    // 记录错误
    console.error('Failed to fetch data:', error);

    // 设置用户友好的错误信息
    if (error.code === 'NETWORK_ERROR') {
      setError('网络连接失败，请检查您的网络');
    } else if (error.code === 'AUTH_ERROR') {
      setError('您的登录已过期，请重新登录');
    } else {
      setError('数据加载失败，请稍后重试');
    }

    // 可选：上报错误到监控系统
    reportError(error);
  } finally {
    setLoading(false);
  }
}
```

### 边界情况处理

```tsx
// ✅ 好的做法 - 处理各种边界情况
function DataDisplay({ data }: { data?: Data[] }) {
  // 处理 undefined
  if (!data) {
    return <LoadingView />;
  }

  // 处理空数组
  if (data.length === 0) {
    return <EmptyView message="暂无数据" />;
  }

  // 正常展示
  return (
    <view>
      {data.map(item => (
        <ItemCard key={item.id} item={item} />
      ))}
    </view>
  );
}
```

---

## 📱 用户体验

### 加载状态

```tsx
// ✅ 好的做法 - 提供清晰的加载反馈
function DataPage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  if (loading) {
    return (
      <view className="loading-container">
        <loading-spinner />
        <text>加载中...</text>
      </view>
    );
  }

  return <DataView data={data} />;
}
```

### 错误反馈

```tsx
// ✅ 好的做法 - 提供可操作的错误提示
function ErrorView({ error, onRetry }: Props) {
  return (
    <view className="error-container">
      <image src="/assets/error-icon.png" className="error-icon" />
      <text className="error-message">{error}</text>
      <button className="retry-button" onClick={onRetry}>
        重试
      </button>
    </view>
  );
}
```

### 交互反馈

```tsx
// ✅ 好的做法 - 提供即时反馈
function SubmitButton({ onSubmit }: Props) {
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = async () => {
    setSubmitting(true);
    try {
      await onSubmit();
      showToast({ message: '提交成功' });
    } catch (error) {
      showToast({ message: '提交失败，请重试' });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <button
      className="submit-button"
      onClick={handleSubmit}
      disabled={submitting}
    >
      {submitting ? '提交中...' : '提交'}
    </button>
  );
}
```

---

## 📝 代码注释

### 何时添加注释

```tsx
// ✅ 好的做法 - 解释"为什么"，而不是"是什么"
function calculateDiscount(price: number, userLevel: string): number {
  // VIP 用户享受额外 10% 折扣（业务需求）
  if (userLevel === 'VIP') {
    return price * 0.9;
  }

  return price;
}

// ❌ 不好的做法 - 注释重复代码
function calculateDiscount(price: number, userLevel: string): number {
  // 如果用户等级是 VIP
  if (userLevel === 'VIP') {
    // 返回价格乘以 0.9
    return price * 0.9;
  }

  // 返回原价
  return price;
}
```

### JSDoc 注释

```tsx
/**
 * 格式化日期为 YYYY-MM-DD 格式
 * @param date - 要格式化的日期对象
 * @param separator - 分隔符，默认为 '-'
 * @returns 格式化后的日期字符串
 * @example
 * formatDate(new Date('2024-01-15')) // '2024-01-15'
 * formatDate(new Date('2024-01-15'), '/') // '2024/01/15'
 */
function formatDate(date: Date, separator: string = '-'): string {
  // 实现...
}
```

---

## 🔗 相关文档

- [开发规则](../rules/dos-and-donts.md)
- [组件开发完整指南](./component-development.md)
- [性能优化](./performance-optimization.md)
- [故障排查](./troubleshooting.md)
