React 条件渲染

在React中,你可以使用JavaScript的条件语句来有条件地渲染组件或元素,实现动态的UI显示。

条件渲染的基本方法

1. if语句

function UserGreeting(props) {
  const isLoggedIn = props.isLoggedIn;

  if (isLoggedIn) {
    return <h1>欢迎回来!</h1>;
  }
  return <h1>请先登录。</h1>;
}

function LoginControl() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleLogin = () => {
    setIsLoggedIn(true);
  };

  const handleLogout = () => {
    setIsLoggedIn(false);
  };

  return (
    <div>
      <UserGreeting isLoggedIn={isLoggedIn} />
      {isLoggedIn ? (
        <button onClick={handleLogout}>退出</button>
      ) : (
        <button onClick={handleLogin}>登录</button>
      )}
    </div>
  );
}

实时演示

请先登录。

2. 元素变量

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      登录
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      退出
    </button>
  );
}

function LoginControlWithVariable() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleLoginClick = () => {
    setIsLoggedIn(true);
  };

  const handleLogoutClick = () => {
    setIsLoggedIn(false);
  };

  // 使用变量存储元素
  let button;
  if (isLoggedIn) {
    button = <LogoutButton onClick={handleLogoutClick} />;
  } else {
    button = <LoginButton onClick={handleLoginClick} />;
  }

  return (
    <div>
      <p>用户状态: {isLoggedIn ? '已登录' : '未登录'}</p>
      {button}
    </div>
  );
}

3. 逻辑与运算符 (&&)

当条件为true时,渲染右侧的元素;条件为false时,什么都不渲染。

function MessageList(props) {
  const messages = props.messages;

  return (
    <div>
      <h3>消息列表</h3>
      {messages.length > 0 &&
        <p>
          你有 {messages.length} 条未读消息。
        </p>
      }
      <ul>
        {messages.map(message => (
          <li key={message.id}>{message.text}</li>
        ))}
      </ul>
    </div>
  );
}

// 使用组件
const messages = [
  { id: 1, text: '消息1' },
  { id: 2, text: '消息2' }
];

// 渲染 MessageList 组件
<MessageList messages={messages} />
注意: 使用逻辑与运算符时,确保左侧的条件表达式返回布尔值。如果返回的是数字0,React会渲染0而不是什么都不渲染。

4. 三元运算符 (condition ? true : false)

function Notification(props) {
  const isImportant = props.isImportant;
  const message = props.message;

  return (
    <div className={`notification ${isImportant ? 'important' : 'normal'}`}>
      {isImportant ? (
        <div>
          <strong><i className="fas fa-exclamation-circle"></i> 重要: </strong>
          {message}
        </div>
      ) : (
        <div>
          <i className="fas fa-info-circle"></i> 通知: {message}
        </div>
      )}
    </div>
  );
}

// 更复杂的三元运算符嵌套(不推荐)
function ComplexTernaryExample() {
  const [status, setStatus] = useState('loading');
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  // 不推荐的写法:嵌套过深难以阅读
  const renderContent = () => {
    return status === 'loading' ? (
      <div>加载中...</div>
    ) : status === 'error' ? (
      <div>错误: {error}</div>
    ) : (
      <div>数据: {JSON.stringify(data)}</div>
    );
  };

  return (
    <div>
      {renderContent()}
    </div>
  );
}

防止组件渲染

在某些情况下,你可能希望隐藏组件,即使它已经被其他组件渲染。这时可以让 render 方法直接返回 null,而不进行任何渲染。

function WarningBanner(props) {
  if (!props.warn) {
    return null; // 不渲染任何内容
  }

  return (
    <div className="warning">
      <i className="fas fa-exclamation-triangle"></i>
      警告!
    </div>
  );
}

function Page() {
  const [showWarning, setShowWarning] = useState(true);

  const toggleWarning = () => {
    setShowWarning(!showWarning);
  };

  return (
    <div>
      <WarningBanner warn={showWarning} />
      <button onClick={toggleWarning}>
        {showWarning ? '隐藏警告' : '显示警告'}
      </button>
    </div>
  );
}

警告显示/隐藏演示

警告!这是一个重要警告信息。

条件渲染模式对比

方法 语法 适用场景 优点 缺点
if语句 if (condition) { ... } else { ... } 组件级别的条件渲染 清晰易读,适合复杂逻辑 需要在组件外部或函数内部使用
三元运算符 condition ? true : false 简单的二选一渲染 简洁,可在JSX内联使用 嵌套时难以阅读
逻辑与运算符 condition && element 条件为真时渲染元素 非常简洁 条件为假时渲染0的问题
元素变量 let element = condition ? a : b 需要复用条件结果 提高代码可读性 增加变量声明
立即执行函数 {(() => { ... })()} 复杂条件逻辑 灵活,可包含复杂逻辑 语法复杂,影响可读性

实际应用示例

1. 加载状态处理

function UserProfile({ userId }) {
  const [loading, setLoading] = useState(true);
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchUser(userId);
  }, [userId]);

  const fetchUser = async (id) => {
    try {
      setLoading(true);
      // 模拟API调用
      const response = await fetch(`/api/users/${id}`);
      const data = await response.json();
      setUser(data);
      setError(null);
    } catch (err) {
      setError('加载用户数据失败');
      setUser(null);
    } finally {
      setLoading(false);
    }
  };

  // 使用条件渲染处理不同状态
  if (loading) {
    return (
      <div className="loading-spinner">
        <i className="fas fa-spinner fa-spin"></i>
        加载中...
      </div>
    );
  }

  if (error) {
    return (
      <div className="error-message">
        <i className="fas fa-exclamation-circle"></i>
        {error}
      </div>
    );
  }

  if (!user) {
    return (
      <div className="no-data">
        未找到用户信息
      </div>
    );
  }

  return (
    <div className="user-profile">
      <h2>{user.name}</h2>
      <p>邮箱: {user.email}</p>
      <p>角色: {user.role}</p>
    </div>
  );
}

2. 权限控制

// 权限控制组件
function ProtectedRoute({ children, requiredRole }) {
  const { user, loading } = useAuth();

  if (loading) {
    return <div>检查权限中...</div>;
  }

  // 检查用户是否登录
  if (!user) {
    return (
      <div className="unauthorized">
        <h3>需要登录</h3>
        <p>请先登录访问此页面</p>
        <a href="/login">前往登录</a>
      </div>
    );
  }

  // 检查用户角色
  if (requiredRole && user.role !== requiredRole) {
    return (
      <div className="forbidden">
        <h3>权限不足</h3>
        <p>您没有权限访问此页面</p>
      </div>
    );
  }

  // 权限检查通过,渲染子组件
  return children;
}

// 使用示例
function AdminPanel() {
  return (
    <ProtectedRoute requiredRole="admin">
      <div>
        <h1>管理员面板</h1>
        <p>只有管理员能看到这个内容</p>
        {/* 管理员功能 */}
      </div>
    </ProtectedRoute>
  );
}

3. 列表条件渲染

function ProductList({ products, showOutOfStock, sortBy }) {
  // 过滤产品
  let filteredProducts = products;

  if (!showOutOfStock) {
    filteredProducts = products.filter(product => product.inStock);
  }

  // 排序产品
  if (sortBy === 'price') {
    filteredProducts = [...filteredProducts].sort((a, b) => a.price - b.price);
  } else if (sortBy === 'name') {
    filteredProducts = [...filteredProducts].sort((a, b) =>
      a.name.localeCompare(b.name)
    );
  }

  // 条件渲染:空列表处理
  if (filteredProducts.length === 0) {
    return (
      <div className="empty-state">
        <i className="fas fa-box-open"></i>
        <h3>没有找到产品</h3>
        <p>尝试调整筛选条件</p>
      </div>
    );
  }

  // 条件渲染:显示结果数量
  return (
    <div>
      <div className="results-info">
        找到 {filteredProducts.length} 个产品
        {!showOutOfStock && (
          <span className="info-text"> (已隐藏缺货产品)</span>
        )}
      </div>

      <div className="product-grid">
        {filteredProducts.map(product => (
          <ProductCard
            key={product.id}
            product={product}
          />
        ))}
      </div>
    </div>
  );
}

高级技巧和最佳实践

1. 使用组件提取复杂条件逻辑

// 将复杂条件提取为专用组件
function StatusRenderer({ status, data, error }) {
  switch (status) {
    case 'loading':
      return <LoadingSpinner />;
    case 'error':
      return <ErrorMessage error={error} />;
    case 'empty':
      return <EmptyState />;
    case 'success':
      return <DataDisplay data={data} />;
    default:
      return null;
  }
}

// 在主组件中使用
function DataFetcher() {
  const [status, setStatus] = useState('loading');
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  // ... 数据获取逻辑

  return (
    <div>
      <StatusRenderer
        status={status}
        data={data}
        error={error}
      />
    </div>
  );
}

2. 使用枚举或常量定义状态

// 定义状态常量
const Status = {
  LOADING: 'loading',
  SUCCESS: 'success',
  ERROR: 'error',
  EMPTY: 'empty'
};

function DataComponent() {
  const [status, setStatus] = useState(Status.LOADING);

  // 使用switch语句处理不同状态
  const renderContent = () => {
    switch (status) {
      case Status.LOADING:
        return <Loader />;
      case Status.SUCCESS:
        return <Data />;
      case Status.ERROR:
        return <Error />;
      case Status.EMPTY:
        return <Empty />;
      default:
        return null;
    }
  };

  return (
    <div>
      {renderContent()}
    </div>
  );
}

3. 使用Hooks简化条件渲染

// 自定义Hook处理加载状态
function useDataFetcher(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchData();
  }, [url]);

  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch(url);
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return { data, loading, error };
}

// 使用自定义Hook
function UserComponent({ userId }) {
  const { data: user, loading, error } = useDataFetcher(`/api/users/${userId}`);

  if (loading) return <Loader />;
  if (error) return <Error message={error} />;
  if (!user) return <Empty message="用户不存在" />;

  return (
    <div>
      <h2>{user.name}</h2>
      {/* 用户详情 */}
    </div>
  );
}
最佳实践总结:
  1. 保持条件渲染简洁明了
  2. 避免深度嵌套的三元运算符
  3. 使用组件提取复杂条件逻辑
  4. 处理所有可能的状态(加载、错误、空状态等)
  5. 使用枚举或常量提高代码可读性
  6. 考虑使用自定义Hook复用条件逻辑
  7. 为条件渲染添加适当的动画过渡

常见错误与解决方案

// 错误1:在JSX中使用if语句
function WrongExample1() {
  const isVisible = true;

  return (
    <div>
      {/* 错误:if语句不能在JSX中直接使用 */}
      {if (isVisible) {
        return <p>可见内容</p>;
      }}

      {/* 正确:使用三元运算符或逻辑与 */}
      {isVisible && <p>可见内容</p>}
    </div>
  );
}

// 错误2:逻辑与运算符渲染0
function WrongExample2() {
  const items = []; // 空数组
  const count = items.length; // count = 0

  return (
    <div>
      {/* 错误:当count为0时,会渲染0 */}
      {count && <p>有 {count} 个项目</p>}

      {/* 正确:显式转换为布尔值 */}
      {count > 0 && <p>有 {count} 个项目</p>}

      {/* 正确:使用三元运算符 */}
      {count ? <p>有 {count} 个项目</p> : null}
    </div>
  );
}

// 错误3:忘记处理所有条件分支
function WrongExample3({ status }) {
  // 错误:没有处理所有可能的status值
  if (status === 'loading') {
    return <div>加载中...</div>;
  }

  if (status === 'success') {
    return <div>成功</div>;
  }

  // 如果status是'error'或其他值,会返回undefined
  // React会抛出错误

  // 正确:添加默认返回
  return <div>未知状态</div>;
}

// 错误4:条件渲染导致不必要的重新渲染
function WrongExample4() {
  const [count, setCount] = useState(0);
  const [showMessage, setShowMessage] = useState(false);

  // 错误:每次渲染都会创建新函数
  const renderMessage = () => {
    if (showMessage) {
      return <div>消息内容</div>;
    }
    return null;
  };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        计数: {count}
      </button>
      <button onClick={() => setShowMessage(!showMessage)}>
        切换消息
      </button>
      {/* 每次渲染都会调用renderMessage */}
      {renderMessage()}
    </div>
  );

  // 正确:直接在JSX中使用条件
  // {showMessage && <div>消息内容</div>}
}

条件渲染性能演示

项目数: 0 | 过滤: 关闭

条件渲染与动画

import { CSSTransition } from 'react-transition-group';

function AnimatedConditional() {
  const [show, setShow] = useState(false);

  return (
    <div>
      <button onClick={() => setShow(!show)}>
        {show ? '隐藏' : '显示'}内容
      </button>

      <CSSTransition
        in={show}
        timeout={300}
        classNames="fade"
        unmountOnExit
      >
        <div className="conditional-content">
          这个内容会带有动画效果显示和隐藏
        </div>
      </CSSTransition>
    </div>
  );
}

// CSS样式
// .fade-enter { opacity: 0; }
// .fade-enter-active { opacity: 1; transition: opacity 300ms; }
// .fade-exit { opacity: 1; }
// .fade-exit-active { opacity: 0; transition: opacity 300ms; }