📚 现代前端开发 目录
阶段3:React
第 15 / 35 课

React入门:JSX与组件

组件化思维的开始

📖 核心概念

💻 代码实现

React组件基础 ✅ tsx
// UserCard.tsx - 可复用组件
import { type ReactNode } from 'react';

interface UserCardProps {
  name: string;
  avatar: string;
  bio?: string;
  tags?: string[];
  children?: ReactNode;
  variant?: 'default' | 'compact';
}

export function UserCard({
  name, avatar, bio, tags = [], children, variant = 'default',
}: UserCardProps) {
  const isCompact = variant === 'compact';

  return (
    <article className={\`user-card \${isCompact ? 'compact' : ''}\`}>
      <img src={avatar} alt={name} className="avatar" />
      <div className="info">
        <h3>{name}</h3>
        {!isCompact && bio && <p className="bio">{bio}</p>}
        {tags.length > 0 && (
          <div className="tags">
            {tags.map(tag => (
              <span key={tag} className="tag">#{tag}</span>
            ))}
          </div>
        )}
        {children && <div className="card-footer">{children}</div>}
      </div>
    </article>
  );
}

// App.tsx - 组合使用
import { UserCard } from './UserCard';

function App() {
  const users = [
    { id: 1, name: '张三', avatar: '/zhang.jpg', bio: '前端工程师', tags: ['React', 'TypeScript'] },
    { id: 2, name: '李四', avatar: '/li.jpg', bio: '全栈开发', tags: ['Vue', 'Node.js'] },
    { id: 3, name: '王五', avatar: '/wang.jpg', tags: ['设计'] },
  ];

  return (
    <div className="app">
      <h1>用户列表</h1>
      {/* 列表渲染 */}
      {users.map(user => (
        <UserCard key={user.id} {...user}>
          <button onClick={() => console.log(\`关注 \${user.name}\`)}>
            关注
          </button>
        </UserCard>
      ))}

      {/* 紧凑模式 */}
      <h2>推荐关注</h2>
      {users.slice(0, 2).map(user => (
        <UserCard key={user.id} {...user} variant="compact" />
      ))}
    </div>
  );
}
组件模式与组合 ✅ tsx
// Layout组件 - 组合模式
function Layout({ header, sidebar, children, footer }: {
  header: ReactNode;
  sidebar?: ReactNode;
  children: ReactNode;
  footer?: ReactNode;
}) {
  return (
    <div className="layout">
      <header className="layout-header">{header}</header>
      <div className="layout-body">
        {sidebar && <aside className="layout-sidebar">{sidebar}</aside>}
        <main className="layout-content">{children}</main>
      </div>
      {footer && <footer className="layout-footer">{footer}</footer>}
    </div>
  );
}

// Render Props模式
interface DataFetcherProps<T> {
  url: string;
  render: (state: { data: T | null; loading: boolean; error: Error | null }) => ReactNode;
}

function DataFetcher<T>({ url, render }: DataFetcherProps<T>) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    fetch(url)
      .then(r => r.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);

  return <>{render({ data, loading, error })}</>;
}

// 使用Render Props
<DataFetcher<{ name: string }[]> url="/api/users" render={({ data, loading, error }) => {
  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  return <UserList users={data!} />;
}} />

// 条件渲染技巧
function StatusBadge({ status }: { status: 'active' | 'inactive' | 'pending' }) {
  return (
    <span className={\`badge badge-\${status}\`}>
      {{ active: '✅ 活跃', inactive: '❌ 停用', pending: '⏳ 待审核' }[status]}
    </span>
  );
}

// 短路渲染
function Notification({ count }: { count: number }) {
  return (
    <div className="notification">
      🔔 {count > 0 && <span className="badge">{count > 99 ? '99+' : count}</span>}
    </div>
  );
}

🎯 练习任务

🏆 成就解锁
组件建筑师 — 掌握React组件化思维,构建可复用的UI组件