Skip to content

React 规范

核心原则

React 组件必须使用函数式组件和 Hooks,遵循 React 19 最佳实践,确保组件的可复用性。

允许的做法

组件定义

  • 使用 函数式组件
  • 使用 TypeScript 定义 Props
tsx
// ✅ 正确
import { cn } from '@/lib/utils';

interface UserCardProps {
  user: User;
  onEdit?: (id: number) => void;
  className?: string;
}

function UserCard({ user, onEdit, className }: UserCardProps) {
  return (
    <div className={cn('rounded-lg border bg-card p-4', className)}>
      <h3 className="text-lg font-semibold">{user.name}</h3>
      <button
        className="mt-2 px-4 py-2 bg-primary text-primary-foreground rounded-md"
        onClick={() => onEdit?.(user.id)}
      >
        编辑
      </button>
    </div>
  );
}

export default UserCard;

Hooks 使用

tsx
// ✅ 正确
function UserList() {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    loadUsers();
  }, []);

  const loadUsers = async () => {
    setLoading(true);
    const data = await userService.getList();
    setUsers(data);
    setLoading(false);
  };

  if (loading) {
    return <div className="flex justify-center py-8">加载中...</div>;
  }

  return <div className="space-y-4">{/* 渲染 */}</div>;
}

条件渲染

tsx
// ✅ 正确
function UserProfile({ user }: { user?: User }) {
  if (!user) {
    return (
      <div className="text-center text-muted-foreground py-8">
        用户不存在
      </div>
    );
  }

  return (
    <div className="space-y-2">
      <h1 className="text-2xl font-bold">{user.name}</h1>
      {user.isVip && (
        <span className="inline-flex items-center px-2 py-1 rounded-full text-xs bg-primary text-primary-foreground">
          VIP
        </span>
      )}
    </div>
  );
}

列表渲染

tsx
// ✅ 正确
function UserList({ users }: { users: User[] }) {
  return (
    <ul className="space-y-2">
      {users.map(user => (
        <li key={user.id}>
          <UserCard user={user} />
        </li>
      ))}
    </ul>
  );
}

自定义 Hooks

tsx
// ✅ 正确
function useUser(id: number) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    setLoading(true);
    setError(null);
    userService.getById(id)
      .then(setUser)
      .catch((e) => setError(e.message))
      .finally(() => setLoading(false));
  }, [id]);

  return { user, loading, error };
}

禁止的做法

  • 禁止 使用 Class 组件
  • 禁止 在条件/循环中调用 Hooks
tsx
// ❌ 错误
class UserCard extends React.Component {}

function BadComponent() {
  if (condition) {
    useState(0);  // 禁止
  }
}

性能优化

tsx
// ✅ 正确 - 使用 memo
const UserCard = React.memo(({ user }: { user: User }) => {
  return <div className="p-4 border rounded-lg">{user.name}</div>;
});

// ✅ 正确 - useMemo
const total = useMemo(
  () => products.reduce((sum, p) => sum + p.price, 0),
  [products]
);

// ✅ 正确 - useCallback
const handleClick = useCallback((id: number) => {
  console.log('clicked', id);
}, []);

相关文档