在这里插入图片描述

JSX语法详解

一、JSX是什么?

1.1 5W1H分析

/**
 * 5W1H 分析:JSX
 * 
 * What: JavaScript XML,在 JavaScript 中编写 HTML 的语法扩展
 * Why: 声明式 UI 开发,代码更直观、更易维护
 * Who: 所有 React 开发者
 * When: 编写 React 组件时
 * Where: React 组件的 return 语句中
 * How: 在 JS 中直接写 HTML 标签,使用 {} 嵌入表达式
 */

console.log("=" .repeat(60));
console.log("React 19 学习路线 - 第2篇:JSX语法详解");
console.log("=" .repeat(60));

// JSX 示例
const element = <h1 className="greeting">Hello, React 19!</h1>;

// 编译后的 JavaScript
// const element = React.createElement('h1', { className: 'greeting' }, 'Hello, React 19!');

1.2 为什么使用JSX?

/**
 * JSX 的优势
 * 1. 声明式:代码更直观,易于理解
 * 2. 组件化:便于构建可复用的UI组件
 * 3. 类型安全:编译时检查错误
 * 4. 性能优化:编译时进行优化
 */

// 不使用JSX(纯JavaScript)
const withoutJSX = React.createElement(
  'div',
  { className: 'container' },
  React.createElement('h1', null, '标题'),
  React.createElement('p', null, '段落内容')
);

// 使用JSX(推荐)
const withJSX = (
  <div className="container">
    <h1>标题</h1>
    <p>段落内容</p>
  </div>
);

二、JSX基础语法

2.1 嵌入变量和表达式

function ExpressionDemo() {
  // 1. 嵌入变量
  const name = "React学习者";
  const age = 25;
  const isActive = true;
  const user = { firstName: '张', lastName: '三' };
  
  // 2. 嵌入表达式
  const fullName = `${user.firstName}${user.lastName}`;
  const isAdult = age >= 18;
  const greeting = `你好,${name}!`;
  
  // 3. 嵌入函数调用
  const formatDate = (date) => {
    return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
  };
  
  // 4. 嵌入运算
  const doubleAge = age * 2;
  const messageCount = 5;
  
  return (
    <div className="expression-demo">
      {/* 嵌入变量 */}
      <h2>{greeting}</h2>
      
      {/* 嵌入表达式 */}
      <p>全名: {fullName}</p>
      <p>是否成年: {isAdult ? '是' : '否'}</p>
      
      {/* 嵌入函数调用 */}
      <p>当前日期: {formatDate(new Date())}</p>
      
      {/* 嵌入运算 */}
      <p>年龄的两倍: {doubleAge}</p>
      
      {/* 条件显示 */}
      {messageCount > 0 && (
        <div className="notification">
          你有 {messageCount} 条新消息
        </div>
      )}
    </div>
  );
}

2.2 属性绑定

function AttributeBinding() {
  // 1. 标准属性
  const className = "btn-primary";
  const disabled = true;
  const id = "submit-btn";
  
  // 2. 布尔属性
  const isChecked = true;
  const isDisabled = false;
  
  // 3. 事件属性(驼峰命名)
  const handleClick = () => console.log('clicked');
  const handleMouseEnter = () => console.log('mouse entered');
  
  // 4. 动态属性名
  const attributeName = "data-custom-id";
  const attributeValue = "12345";
  
  // 5. 展开属性(Spread Attributes)
  const buttonProps = {
    type: "button",
    className: "btn",
    onClick: () => console.log('clicked'),
    disabled: false
  };
  
  // 6. 条件属性
  const isPrimary = true;
  const buttonClass = `btn ${isPrimary ? 'btn-primary' : 'btn-secondary'}`;
  
  return (
    <div className="attribute-demo">
      {/* 标准属性 */}
      <button id={id} className={className} disabled={disabled}>
        提交
      </button>
      
      {/* 布尔属性 */}
      <input type="checkbox" checked={isChecked} readOnly />
      <button disabled={isDisabled}>不可用按钮</button>
      
      {/* 事件属性 */}
      <button onClick={handleClick} onMouseEnter={handleMouseEnter}>
        悬停或点击
      </button>
      
      {/* 动态属性名 */}
      <div {...{ [attributeName]: attributeValue }}>
        动态属性内容
      </div>
      
      {/* 展开属性 */}
      <button {...buttonProps}>展开属性按钮</button>
      
      {/* 条件属性 */}
      <button className={buttonClass}>条件样式按钮</button>
    </div>
  );
}

2.3 子元素

function ChildrenDemo() {
  // 1. 字符串子元素
  const textElement = <div>纯文本内容</div>;
  
  // 2. 元素子元素
  const elementElement = (
    <div>
      <h1>标题</h1>
      <p>段落</p>
    </div>
  );
  
  // 3. 表达式子元素
  const items = ['苹果', '香蕉', '橙子'];
  const listElement = (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
  
  // 4. 函数子元素(render props)
  const DataProvider = ({ render }) => {
    const data = { name: 'React', version: '19' };
    return render(data);
  };
  
  // 5. 混合子元素
  const MixedChildren = () => (
    <div>
      文本内容
      <span>元素内容</span>
      {[1, 2, 3].map(n => (
        <span key={n}> {n} </span>
      ))}
      <DataProvider render={data => (
        <p>{data.name} v{data.version}</p>
      )} />
    </div>
  );
  
  return (
    <div className="children-demo">
      {textElement}
      {elementElement}
      {listElement}
      <MixedChildren />
    </div>
  );
}

2.4 注释语法

function CommentsDemo() {
  // JavaScript单行注释(在JSX外部)
  
  /* JavaScript多行注释(在JSX外部) */
  
  return (
    <div className="comments-demo">
      {/* JSX注释语法 */}
      
      {/* 
        多行JSX注释
        可以写多行内容
      */}
      
      <h1>标题</h1>
      
      {/* 条件渲染中的注释 */}
      {true && (
        /* 内联注释 */
        <p>显示内容</p>
      )}
      
      {/* 使用注释来说明代码意图 */}
      <button
        onClick={() => console.log('clicked')}
        /* 这是一个重要的按钮 */
        className="important-btn"
      >
        重要按钮
      </button>
    </div>
  );
}

三、JSX中的条件渲染

3.1 if语句方式

function IfConditionDemo({ isLoggedIn, userRole }) {
  // 方式1:组件内使用if语句
  let greeting;
  if (isLoggedIn) {
    greeting = <h2>欢迎回来!</h2>;
  } else {
    greeting = <h2>请先登录</h2>;
  }
  
  // 方式2:立即执行函数(IIFE)
  const renderContent = () => {
    if (userRole === 'admin') {
      return <AdminPanel />;
    } else if (userRole === 'user') {
      return <UserPanel />;
    } else {
      return <GuestPanel />;
    }
  };
  
  return (
    <div className="if-demo">
      {greeting}
      {renderContent()}
    </div>
  );
}

// 辅助组件
function AdminPanel() { return <div>管理员面板</div>; }
function UserPanel() { return <div>用户面板</div>; }
function GuestPanel() { return <div>游客面板</div>; }

3.2 三元运算符

function TernaryDemo({ isLoggedIn, theme, hasNotification }) {
  return (
    <div className="ternary-demo">
      {/* 基础三元运算符 */}
      <h2>{isLoggedIn ? '欢迎回来' : '请登录'}</h2>
      
      {/* 嵌套三元运算符(谨慎使用) */}
      <p>
        状态: {isLoggedIn 
          ? '已登录' 
          : theme === 'dark' 
            ? '暗色模式游客' 
            : '亮色模式游客'
        }
      </p>
      
      {/* 三元运算符渲染组件 */}
      <div>
        {isLoggedIn ? (
          <UserDashboard />
        ) : (
          <LoginForm />
        )}
      </div>
      
      {/* 三元运算符设置属性 */}
      <button className={hasNotification ? 'has-notification' : ''}>
        消息
        {hasNotification && <span className="badge">!</span>}
      </button>
    </div>
  );
}

3.3 &&运算符

function AndOperatorDemo({ isLoggedIn, notifications, user }) {
  return (
    <div className="and-demo">
      {/* 基础&&用法 */}
      {isLoggedIn && <WelcomeMessage />}
      
      {/* 与0的陷阱(0 && something 会渲染0) */}
      {notifications.length > 0 && (
        <div className="notification">
          你有 {notifications.length} 条通知
        </div>
      )}
      
      {/* 链式&& */}
      {isLoggedIn && user.isVIP && <VIPBadge />}
      
      {/* 多条件组合 */}
      {isLoggedIn && notifications.length > 0 && (
        <NotificationList notifications={notifications} />
      )}
      
      {/* 防止渲染false */}
      {isLoggedIn && null}  {/* 不渲染任何内容 */}
    </div>
  );
}

3.4 switch语句

function SwitchDemo({ status }) {
  // 方式1:switch语句
  let statusMessage;
  switch (status) {
    case 'loading':
      statusMessage = <LoadingSpinner />;
      break;
    case 'success':
      statusMessage = <SuccessMessage />;
      break;
    case 'error':
      statusMessage = <ErrorMessage />;
      break;
    default:
      statusMessage = <InitialState />;
  }
  
  // 方式2:对象映射(推荐)
  const statusComponents = {
    loading: <LoadingSpinner />,
    success: <SuccessMessage />,
    error: <ErrorMessage />,
    idle: <InitialState />
  };
  
  return (
    <div className="switch-demo">
      {statusMessage}
      {statusComponents[status] || statusComponents.idle}
    </div>
  );
}

四、JSX中的列表渲染

4.1 基础列表渲染

function BasicListDemo() {
  // 1. 数组渲染
  const fruits = ['苹果', '香蕉', '橙子', '葡萄'];
  
  // 2. 对象数组渲染
  const users = [
    { id: 1, name: '张三', age: 25 },
    { id: 2, name: '李四', age: 30 },
    { id: 3, name: '王五', age: 28 }
  ];
  
  // 3. 带索引的列表
  const items = ['项目A', '项目B', '项目C'];
  
  return (
    <div className="list-demo">
      <h3>水果列表</h3>
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
      
      <h3>用户列表</h3>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.name} - {user.age}岁
          </li>
        ))}
      </ul>
      
      <h3>带索引的列表</h3>
      <ul>
        {items.map((item, index) => (
          <li key={index}>
            第{index + 1}项: {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

4.2 过滤和排序

function FilterSortDemo() {
  const [filter, setFilter] = useState('all');
  const [sortBy, setSortBy] = useState('name');
  
  const products = [
    { id: 1, name: '苹果', category: '水果', price: 10, inStock: true },
    { id: 2, name: '香蕉', category: '水果', price: 8, inStock: true },
    { id: 3, name: '白菜', category: '蔬菜', price: 5, inStock: false },
    { id: 4, name: '番茄', category: '蔬菜', price: 6, inStock: true },
    { id: 5, name: '橙子', category: '水果', price: 12, inStock: true }
  ];
  
  // 过滤
  const filteredProducts = products.filter(product => {
    if (filter === 'inStock') return product.inStock;
    if (filter === 'outOfStock') return !product.inStock;
    return true;
  });
  
  // 排序
  const sortedProducts = [...filteredProducts].sort((a, b) => {
    if (sortBy === 'name') return a.name.localeCompare(b.name);
    if (sortBy === 'price') return a.price - b.price;
    return 0;
  });
  
  return (
    <div className="filter-sort-demo">
      <div className="controls">
        <select value={filter} onChange={(e) => setFilter(e.target.value)}>
          <option value="all">全部</option>
          <option value="inStock">有货</option>
          <option value="outOfStock">缺货</option>
        </select>
        
        <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
          <option value="name">按名称排序</option>
          <option value="price">按价格排序</option>
        </select>
      </div>
      
      <ul>
        {sortedProducts.map(product => (
          <li key={product.id} className={!product.inStock ? 'out-of-stock' : ''}>
            <span>{product.name}</span>
            <span>¥{product.price}</span>
            <span>{product.inStock ? '有货' : '缺货'}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

4.3 分组渲染

function GroupRenderDemo() {
  const groupedData = [
    { category: '水果', items: ['苹果', '香蕉', '橙子'] },
    { category: '蔬菜', items: ['白菜', '萝卜', '番茄'] },
    { category: '饮料', items: ['可乐', '雪碧', '果汁'] }
  ];
  
  // 嵌套列表
  const nestedData = [
    { id: 1, name: '组1', children: [
      { id: 11, name: '子项1' },
      { id: 12, name: '子项2' }
    ]},
    { id: 2, name: '组2', children: [
      { id: 21, name: '子项3' },
      { id: 22, name: '子项4' }
    ]}
  ];
  
  // 树形结构组件
  const TreeNode = ({ node }) => {
    const [expanded, setExpanded] = useState(false);
    
    return (
      <div className="tree-node">
        <div onClick={() => setExpanded(!expanded)} className="node-header">
          {node.children && (
            <span className="toggle">{expanded ? '▼' : '▶'}</span>
          )}
          {node.name}
        </div>
        {expanded && node.children && (
          <div className="node-children">
            {node.children.map(child => (
              <TreeNode key={child.id} node={child} />
            ))}
          </div>
        )}
      </div>
    );
  };
  
  return (
    <div className="group-demo">
      <h3>分组列表</h3>
      {groupedData.map((group, idx) => (
        <div key={idx} className="group">
          <h4>{group.category}</h4>
          <ul>
            {group.items.map((item, i) => (
              <li key={i}>{item}</li>
            ))}
          </ul>
        </div>
      ))}
      
      <h3>树形结构</h3>
      {nestedData.map(node => (
        <TreeNode key={node.id} node={node} />
      ))}
    </div>
  );
}

五、Key属性详解

5.1 Key的重要性

/**
 * Key的作用:
 * 1. 帮助React识别哪些元素发生了变化
 * 2. 用于列表渲染时的性能优化
 * 3. 保持组件状态在重新排序时的稳定性
 */

function KeyImportanceDemo() {
  const [items, setItems] = useState([
    { id: 1, text: '学习React', completed: false },
    { id: 2, text: '掌握JSX', completed: false },
    { id: 3, text: '构建项目', completed: false }
  ]);
  
  // 正确使用Key
  const correctList = items.map(item => (
    <li key={item.id}>{item.text}</li>
  ));
  
  // 错误使用Key(使用索引)
  const incorrectList = items.map((item, index) => (
    <li key={index}>{item.text}</li>  // 不推荐
  ));
  
  // 添加新项(演示Key的作用)
  const addItem = () => {
    const newItem = {
      id: Date.now(),
      text: `新任务 ${items.length + 1}`,
      completed: false
    };
    setItems([newItem, ...items]);
  };
  
  const removeItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };
  
  return (
    <div className="key-demo">
      <button onClick={addItem}>添加任务</button>
      
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <span>{item.text}</span>
            <button onClick={() => removeItem(item.id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

5.2 Key的最佳实践

/**
 * Key的选择原则:
 * 1. 使用数据的唯一ID作为key
 * 2. 避免使用数组索引作为key
 * 3. 避免使用随机数作为key
 * 4. Key应该在兄弟节点间唯一
 */

function KeyBestPractices() {
  // ✅ 好的Key(使用唯一ID)
  const goodKeyList = users.map(user => (
    <li key={user.id}>{user.name}</li>
  ));
  
  // ✅ 好的Key(使用组合ID)
  const compositeKeyList = items.map(item => (
    <li key={`${item.type}-${item.id}`}>{item.name}</li>
  ));
  
  // ❌ 坏的Key(使用索引)
  const badKeyList = items.map((item, index) => (
    <li key={index}>{item.name}</li>  // 当列表顺序变化时会有问题
  ));
  
  // ❌ 坏的Key(使用随机数)
  const randomKeyList = items.map(item => (
    <li key={Math.random()}>{item.name}</li>  // 每次渲染都会变化
  ));
  
  return null;
}

六、样式处理

6.1 行内样式

function InlineStyleDemo() {
  // 基础行内样式
  const divStyle = {
    backgroundColor: '#f0f0f0',
    padding: '20px',
    borderRadius: '8px',
    color: '#333'
  };
  
  // 动态样式
  const [isActive, setIsActive] = useState(false);
  const buttonStyle = {
    backgroundColor: isActive ? '#4CAF50' : '#ccc',
    color: 'white',
    padding: '10px 20px',
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer'
  };
  
  // 条件样式
  const [theme, setTheme] = useState('light');
  const themeStyles = {
    light: { backgroundColor: '#fff', color: '#333' },
    dark: { backgroundColor: '#333', color: '#fff' }
  };
  
  return (
    <div style={themeStyles[theme]}>
      <div style={divStyle}>
        <h2 style={{ fontSize: '24px', marginBottom: '10px' }}>
          行内样式示例
        </h2>
        
        <button
          style={buttonStyle}
          onClick={() => setIsActive(!isActive)}
        >
          {isActive ? '激活' : '未激活'}
        </button>
        
        <button
          style={{ marginLeft: '10px' }}
          onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
        >
          切换主题
        </button>
      </div>
    </div>
  );
}

6.2 CSS模块

/* Button.module.css */
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  transition: all 0.3s ease;
}

.primary {
  background-color: #4CAF50;
  color: white;
}

.primary:hover {
  background-color: #45a049;
}

.secondary {
  background-color: #f0f0f0;
  color: #333;
}

.large {
  padding: 15px 30px;
  font-size: 18px;
}

.small {
  padding: 5px 10px;
  font-size: 12px;
}
// Button.jsx
import styles from './Button.module.css';

function Button({ variant = 'primary', size = 'medium', children, onClick }) {
  const sizeClass = {
    small: styles.small,
    medium: '',
    large: styles.large
  }[size];
  
  return (
    <button
      className={`
        ${styles.button}
        ${styles[variant]}
        ${sizeClass}
      `.trim()}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

6.3 CSS-in-JS (Styled Components)

import styled from 'styled-components';

// 定义样式组件
const StyledCard = styled.div`
  background-color: ${props => props.theme === 'dark' ? '#333' : '#fff'};
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  transition: all 0.3s ease;
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.15);
  }
`;

const StyledTitle = styled.h2`
  color: ${props => props.color || '#333'};
  font-size: ${props => props.size || '24px'};
  margin-bottom: 10px;
`;

const StyledButton = styled.button`
  background-color: ${props => props.variant === 'primary' ? '#4CAF50' : '#ccc'};
  color: white;
  padding: ${props => props.size === 'large' ? '12px 24px' : '8px 16px'};
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  &:hover {
    opacity: 0.9;
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

// 使用样式组件
function StyledComponentsDemo() {
  const [theme, setTheme] = useState('light');
  
  return (
    <StyledCard theme={theme}>
      <StyledTitle color="#4CAF50" size="28px">
        CSS-in-JS示例
      </StyledTitle>
      
      <StyledTitle>普通标题</StyledTitle>
      
      <div>
        <StyledButton variant="primary" size="large">
          主要按钮
        </StyledButton>
        <StyledButton variant="secondary" style={{ marginLeft: '10px' }}>
          次要按钮
        </StyledButton>
      </div>
      
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        切换主题
      </button>
    </StyledCard>
  );
}

6.4 动态样式

function DynamicStyleDemo() {
  const [progress, setProgress] = useState(0);
  const [isVisible, setIsVisible] = useState(true);
  const [activeIndex, setActiveIndex] = useState(0);
  
  // 动态计算样式
  const progressBarStyle = {
    width: `${progress}%`,
    height: '100%',
    backgroundColor: progress < 30 ? '#f44336' : progress < 70 ? '#ff9800' : '#4CAF50',
    transition: 'width 0.3s ease'
  };
  
  // 动态类名
  const getTabClassName = (index) => {
    return `tab ${activeIndex === index ? 'active' : ''}`;
  };
  
  // CSS变量
  const cssVariableStyle = {
    '--primary-color': '#4CAF50',
    '--secondary-color': '#ff9800',
    '--font-size': '16px'
  };
  
  return (
    <div className="dynamic-style-demo" style={cssVariableStyle}>
      {/* 动态宽度 */}
      <div className="progress-container">
        <div style={progressBarStyle} />
      </div>
      <button onClick={() => setProgress(p => Math.min(100, p + 10))}>
        增加进度
      </button>
      
      {/* 动态显示/隐藏 */}
      <div style={{ display: isVisible ? 'block' : 'none' }}>
        可见内容
      </div>
      <button onClick={() => setIsVisible(!isVisible)}>
        切换显示
      </button>
      
      {/* 动态类名 */}
      <div className="tabs">
        {['标签1', '标签2', '标签3'].map((tab, index) => (
          <button
            key={index}
            className={getTabClassName(index)}
            onClick={() => setActiveIndex(index)}
          >
            {tab}
          </button>
        ))}
      </div>
    </div>
  );
}

七、常见陷阱与注意事项

7.1 常见错误

/**
 * JSX常见陷阱
 */

function CommonPitfalls() {
  // ❌ 错误:忘记闭合标签
  // <img src="image.jpg">
  
  // ✅ 正确:自闭合标签
  const correctImg = <img src="image.jpg" alt="描述" />;
  
  // ❌ 错误:使用class而不是className
  // <div class="container">内容</div>
  
  // ✅ 正确:使用className
  const correctDiv = <div className="container">内容</div>;
  
  // ❌ 错误:使用for而不是htmlFor
  // <label for="input">标签</label>
  
  // ✅ 正确:使用htmlFor
  const correctLabel = <label htmlFor="input">标签</label>;
  
  // ❌ 错误:在JSX中使用if语句
  // const element = if (condition) { ... }
  
  // ✅ 正确:使用三元运算符或&&
  const correctCondition = condition ? <div>显示</div> : null;
  
  // ❌ 错误:直接在JSX中使用对象
  // <div>{{ name: 'React' }}</div>
  
  // ✅ 正确:对象需要先转换为字符串
  const user = { name: 'React' };
  const correctObject = <div>{JSON.stringify(user)}</div>;
  
  return null;
}

7.2 性能注意事项

/**
 * JSX性能优化
 */

function PerformanceTips() {
  // ✅ 好的做法:在外部定义内联函数
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  // ❌ 不好的做法:每次渲染都创建新函数
  const badClick = () => console.log('clicked'); // 但可以配合useCallback
  
  // ✅ 好的做法:使用useMemo缓存计算结果
  const expensiveValue = useMemo(() => {
    return heavyComputation(data);
  }, [data]);
  
  // ✅ 好的做法:条件渲染时避免创建不必要的元素
  const conditionalElement = isVisible && <div>可见内容</div>;
  
  // ✅ 好的做法:列表渲染使用稳定的key
  const list = items.map(item => (
    <ListItem key={item.id} item={item} />
  ));
  
  return (
    <div>
      <button onClick={handleClick}>点击</button>
      {conditionalElement}
      {list}
    </div>
  );
}

八、总结

8.1 知识点回顾

知识点 说明 重要程度
JSX基础语法 嵌入表达式、属性绑定 ⭐⭐⭐⭐⭐
条件渲染 if、三元、&&、switch ⭐⭐⭐⭐⭐
列表渲染 map、key、过滤、排序 ⭐⭐⭐⭐⭐
样式处理 行内样式、CSS模块、CSS-in-JS ⭐⭐⭐⭐
常见陷阱 class vs className、标签闭合 ⭐⭐⭐⭐

8.2 练习任务

// 练习1:创建一个产品卡片组件,包含条件渲染
// 要求:显示产品信息,根据库存状态显示不同样式

function ProductCard({ product }) {
  // 实现代码
  return <div>...</div>;
}

// 练习2:创建一个可排序和过滤的表格组件
// 要求:支持列排序、关键字过滤

function DataTable({ data, columns }) {
  // 实现代码
  return <table>...</table>;
}

// 练习3:创建一个可展开的树形菜单组件
// 要求:支持嵌套结构、展开/收起

function TreeMenu({ data }) {
  // 实现代码
  return <div>...</div>;
}

8.3 下一节预告

下一篇将学习 组件与Props,内容包括:

  • 函数组件 vs 类组件
  • Props传递与类型检查
  • 组件组合模式
  • 高阶组件
  • Render Props

更多推荐