条件渲染
根据不同的条件显示不同的 UI
什么是条件渲染?
条件渲染允许你根据应用程序的状态、用户输入或其他条件, 动态地显示或隐藏组件的某些部分。
在 React 中,你可以使用 JavaScript 的条件语句(如 if、
&&、三元运算符)来实现条件渲染。
核心思想
React 中的条件渲染就是使用 JavaScript 的条件逻辑来决定渲染什么。 与其他框架中的特殊语法不同,React 使用的是普通的 JavaScript。
使用 if 语句
在渲染前使用 if
最直接的方式是在 return 之前使用 if 语句:
TypeScript
function Greeting({ isLoggedIn }) {
let content;
if (isLoggedIn) {
content = <h1>欢迎回来!</h1>;
} else {
content = <h1>请先登录</h1>;
}
return <div>{content}</div>;
}
提前返回
你可以使用提前返回来简化代码:
TypeScript
function Greeting({ isLoggedIn }) {
if (!isLoggedIn) {
return <LoginPrompt />;
}
return <WelcomeMessage />;
}
返回 null
当你不想渲染任何内容时,可以返回 null:
TypeScript
function WarningBanner({ warn }) {
if (!warn) {
return null;
}
return (
<div className="warning">
警告!
</div>
);
}
最佳实践
提前返回通常比嵌套的 if-else 更清晰。 优先考虑使用提前返回或提前返回 null 的方式。
三元运算符
基本用法
三元运算符 condition ? trueValue : falseValue
是在 JSX 中进行条件渲染的常用方式:
TypeScript
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<LogoutButton />
) : (
<LoginButton />
)}
</div>
);
}
内联条件
你可以在 JSX 中内联使用三元运算符:
TypeScript
function UserCard({ user }) {
return (
<div className="card">
<h2>{user.name}</h2>
<p>
状态: {user.isActive ? (
<span className="active">活跃</span>
) : (
<span className="inactive">未激活</span>
)}
</p>
</div>
);
}
三元运算符 vs if
TypeScript
// ✅ 三元运算符:简单的二选一
{isLoggedIn ? <Dashboard /> : <Login />}
// ❌ 不要用于复杂条件
{condition1 ? (
condition2 ? <A /> : <B />
) : (
condition3 ? <C /> : <D />
)}
// ✅ 复杂条件使用函数
function renderContent() {
if (condition1) {
return condition2 ? <A /> : <B />;
}
return condition3 ? <C /> : <D />;
}
{renderContent()}
注意
虽然三元运算符很方便,但嵌套的三元运算符会使代码难以阅读。 当条件复杂时,考虑使用函数或提前返回。
逻辑与运算符 (&&)
基本用法
使用 && 运算符,当条件为 true 时才渲染元素:
TypeScript
function Cart({ items }) {
return (
<div>
<h1>购物车</h1>
{items.length > 0 && (
<p>你有 {items.length} 件商品</p>
)}
</div>
);
}
工作原理
在 JavaScript 中,true && expression 返回 expression,
而 false && expression 返回 false:
TypeScript
true && <div>Hello</div> // 渲染 <div>Hello</div>
false && <div>Hello</div> // 不渲染任何内容
"hello" && <div>Hi</div> // 渲染 <div>Hi</div>
"" && <div>Hi</div> // 不渲染任何内容
常见用例
TypeScript
function TaskList({ tasks }) {
return (
<div>
<h2>任务列表</h2>
{/* 显示未完成任务数 */}
{tasks.filter(t => !t.completed).length > 0 && (
<p className="info">
你有 {tasks.filter(t => !t.completed).length} 个未完成任务
</p>
)}
{/* 渲染任务列表 */}
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.text}
</li>
))}
</ul>
</div>
);
}
常见陷阱
不要在 && 的左侧使用数字:
TypeScript
// ❌ 错误:0 会被渲染
{items.length && <ItemList items={items} />}
// ✅ 正确:明确比较
{items.length > 0 && <ItemList items={items} />}
逻辑或运算符 (||)
提供默认值
使用 || 运算符提供默认值:
TypeScript
function UserProfile({ user }) {
return (
<div>
<h2>{user.name || '匿名用户'}</h2>
<p>{user.bio || '这个人很懒,什么都没写'}</p>
</div>
);
}
Nullish Coalescing (??)
使用 ?? 运算符只在值为 null 或 undefined 时使用默认值:
TypeScript
function Display({ count, name }) {
return (
<div>
{/* || 会认为 0 是 falsy */}
<p>数量: {count || '未知'}</p> {/* 0 会显示为 "未知" */}
{/* ?? 只在 null/undefined 时使用默认值 */}
<p>数量: {count ?? '未知'}</p> {/* 0 会正常显示 */}
{/* 空字符串 */}
<p>名称: {name || '未命名'}</p> {/* "" 会显示为 "未命名" */}
<p>名称: {name ?? '未命名'}</p> {/* "" 会正常显示 */}
</div>
);
}
选择合适的运算符
- &&:条件为真时渲染
- ||:falsy 值时使用默认值
- ??:null/undefined 时使用默认值
组件级别的条件渲染
条件性渲染组件
TypeScript
function Page({ user, isLoading }) {
if (isLoading) {
return <LoadingSpinner />;
}
if (!user) {
return <LoginForm />;
}
return <Dashboard user={user} />;
}
包装组件
TypeScript
function ConditionalWrapper({ condition, wrapper, children }) {
return condition ? wrapper(children) : children;
}
// 使用
<ConditionalWrapper
condition={isLink}
wrapper={children => <a href="/">{children}</a>}
>
<button>点击</button>
</ConditionalWrapper>
高阶组件 (HOC)
TypeScript
function withAuth(WrappedComponent) {
return function AuthComponent(props) {
const { isAuthenticated, ...rest } = props;
if (!isAuthenticated) {
return <LoginPrompt />;
}
return <WrappedComponent {...rest} />;
};
}
// 使用
const ProtectedPage = withAuth(Dashboard);
<ProtectedPage isAuthenticated={true} />
隐藏 vs 不渲染
CSS 隐藏
TypeScript
function Panel({ isVisible, children }) {
return (
<div style={{ display: isVisible ? 'block' : 'none' }}>
{children}
</div>
);
}
// 元素仍然在 DOM 中,只是不可见
<Panel isVisible={false}>
<input type="text" />
</Panel>
条件渲染
TypeScript
function Panel({ isVisible, children }) {
if (!isVisible) {
return null;
}
return <div>{children}</div>;
}
// 元素不在 DOM 中
<Panel isVisible={false}>
<input type="text" />
</Panel>
如何选择?
| 方法 | DOM 存在 | 保留状态 | 性能 | 适用场景 |
|---|---|---|---|---|
| CSS 隐藏 | ✅ | ✅ | 快 | 频繁切换,保留状态 |
| 条件渲染 | ❌ | ❌ | 更快 | 完全不需要时 |
性能考虑
如果组件包含昂贵的初始化或大量数据,使用 CSS 隐藏可以避免重新创建组件。 但要注意隐藏的组件仍在内存中。
常见模式
加载状态
TypeScript
function DataList({ data, isLoading, error }) {
if (isLoading) {
return <Spinner />;
}
if (error) {
return <ErrorMessage error={error} />;
}
if (!data || data.length === 0) {
return <EmptyState />;
}
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
权限控制
TypeScript
function AdminPanel({ user }) {
if (!user) {
return <LoginPrompt />;
}
if (!user.isAdmin) {
return <AccessDenied />;
}
return <AdminDashboard />;
}
多条件渲染
TypeScript
function getStatusBadge(status) {
switch (status) {
case 'success':
return <Badge variant="success">成功</Badge>;
case 'warning':
return <Badge variant="warning">警告</Badge>;
case 'error':
return <Badge variant="error">错误</Badge>;
default:
return <Badge variant="info">未知</Badge>;
}
}
function Task({ task }) {
return (
<div>
<span>{task.name}</span>
{getStatusBadge(task.status)}
</div>
);
}
枚举模式
TypeScript
const StateComponents = {
loading: LoadingSpinner,
error: ErrorDisplay,
success: SuccessMessage,
empty: EmptyState,
};
function DataDisplay({ state, data }) {
const Component = StateComponents[state];
return <Component data={data} />;
}
提示
对于多种状态的渲染,使用对象映射比多个 if-else 或三元运算符更清晰。 这种模式称为"枚举模式"。
最佳实践
1. 保持简单
TypeScript
// ✅ 好:简单明了
{isLoggedIn && <Dashboard />}
// ❌ 差:不必要的复杂
{isLoggedIn === true ? <Dashboard /> : null}
2. 提取复杂逻辑
TypeScript
// ❌ 差:JSX 中有复杂逻辑
function UserList({ users }) {
return (
<div>
{users.filter(u => u.isActive).map(u => (
<div key={u.id}>
{u.name} {u.role === 'admin' && '(管理员)'}
</div>
))}
</div>
);
}
// ✅ 好:提取逻辑
function UserList({ users }) {
const activeUsers = users.filter(u => u.isActive);
return (
<div>
{activeUsers.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
3. 使用明确的条件
TypeScript
// ❌ 差:隐式转换
{items.length && <List />}
// ✅ 好:明确比较
{items.length > 0 && <List />}
4. 避免深层嵌套
TypeScript
// ❌ 差:深层嵌套
{condition1 && (
condition2 ? (
condition3 ? <A /> : <B />
) : (
<C />
)
)}
// ✅ 好:使用提前返回
function renderContent() {
if (!condition1) return <C />;
if (!condition2) return <C />;
if (condition3) return <A />;
return <B />;
}
5. 使用 TypeScript
TypeScript
interface Props {
status: 'loading' | 'success' | 'error';
data?: DataType;
error?: Error;
}
function DataDisplay({ status, data, error }: Props) {
switch (status) {
case 'loading':
return <Spinner />;
case 'success':
return <DataView data={data!} />;
case 'error':
return <ErrorView error={error!} />;
}
}
常见问题
条件不工作?
确保你在比较值时使用正确的运算符:
TypeScript
// ❌ 错误:赋值而不是比较
if (value = 'hello') { }
// ✅ 正确:比较
if (value === 'hello') { }
数字 0 不显示?
TypeScript
// ❌ 错误:0 是 falsy
{count && <span>{count}</span>}
// ✅ 正确:明确比较
{count > 0 && <span>{count}</span>}
如何处理多个条件?
TypeScript
// 使用函数处理复杂逻辑
function shouldShowComponent(props) {
return props.isEnabled &&
props.isVisible &&
props.hasPermission;
}
{shouldShowComponent(props) && <Component />}
三元运算符嵌套太深?
TypeScript
// 将复杂的条件逻辑提取为函数
function renderContent(props) {
if (props.isLoading) return <Loading />;
if (props.error) return <Error />;
if (!props.data) return <Empty />;
return <Content data={props.data} />;
}
{renderContent(props)}