Appearance
逻辑与运算符渲染
学习目标
通过本章学习,你将全面掌握:
- 逻辑与运算符的渲染原理
- &&运算符的正确使用
- 常见陷阱和完整解决方案
- 逻辑或运算符的使用
- 空值合并运算符(??)
- 可选链运算符(?.)
- 各种运算符的组合使用
- React 19中的最佳实践
第一部分:逻辑与运算符基础
1.1 基本用法
jsx
// 语法:condition && expression
// 如果condition为true,返回expression并渲染
// 如果condition为false,返回false(React不渲染false)
function BasicLogicalAnd({ isLoggedIn, hasPermission, isActive }) {
return (
<div>
<h1>主页</h1>
{/* isLoggedIn为true时显示欢迎语 */}
{isLoggedIn && <p>欢迎回来!</p>}
{/* hasPermission为true时显示管理面板 */}
{hasPermission && <AdminPanel />}
{/* isActive为true时显示在线状态 */}
{isActive && <span className="status-online">在线</span>}
</div>
);
}1.2 单一条件渲染
jsx
function SingleCondition({
showMessage,
hasNotifications,
isOnline,
isPremium,
isVerified
}) {
return (
<div>
{/* 显示消息 */}
{showMessage && (
<div className="message">
这是一条重要消息
</div>
)}
{/* 显示通知徽章 */}
{hasNotifications && (
<span className="badge">新</span>
)}
{/* 显示在线状态 */}
{isOnline && (
<div className="status-indicator online">
在线
</div>
)}
{/* 显示VIP标志 */}
{isPremium && (
<span className="vip-badge">VIP</span>
)}
{/* 显示认证标志 */}
{isVerified && (
<span className="verified-icon">✓ 已认证</span>
)}
</div>
);
}
// 实际应用:用户卡片
function UserCard({ user }) {
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.email}</p>
{user.isPremium && (
<span className="badge premium">VIP会员</span>
)}
{user.isVerified && (
<span className="badge verified">已验证</span>
)}
{user.isOnline && (
<span className="online-indicator">在线</span>
)}
{user.bio && (
<p className="bio">{user.bio}</p>
)}
{user.location && (
<p className="location">📍 {user.location}</p>
)}
</div>
);
}1.3 逻辑与的短路特性
jsx
function ShortCircuitEvaluation() {
const [count, setCount] = useState(0);
const [enabled, setEnabled] = useState(true);
return (
<div>
<p>计数:{count}</p>
{/* count大于0时才显示 */}
{count > 0 && <p>计数大于0</p>}
{/* 链式条件:所有条件都要满足 */}
{count > 0 && count < 10 && <p>计数在1-9之间</p>}
{/* 多个条件组合 */}
{enabled && count > 5 && count % 2 === 0 && (
<p>已启用,计数大于5且为偶数</p>
)}
{/* 函数调用也适用 */}
{enabled && performCheck() && (
<p>检查通过</p>
)}
<button onClick={() => setCount(c => c + 1)}>增加</button>
<button onClick={() => setEnabled(!enabled)}>
{enabled ? '禁用' : '启用'}
</button>
</div>
);
}
function performCheck() {
console.log('执行检查');
return true;
}第二部分:常见陷阱
2.1 数字0的问题
jsx
// ❌ 问题:0会被渲染
function ZeroProblem({ count }) {
return (
<div>
{/* 当count为0时,会在页面上显示"0" */}
{count && <p>共 {count} 项</p>}
</div>
);
}
// ✅ 解决方案1:显式比较
function Solution1({ count }) {
return (
<div>
{count > 0 && <p>共 {count} 项</p>}
</div>
);
}
// ✅ 解决方案2:布尔转换
function Solution2({ count }) {
return (
<div>
{Boolean(count) && <p>共 {count} 项</p>}
</div>
);
}
// ✅ 解决方案3:双重否定
function Solution3({ count }) {
return (
<div>
{!!count && <p>共 {count} 项</p>}
</div>
);
}
// ✅ 解决方案4:使用三元运算符
function Solution4({ count }) {
return (
<div>
{count ? <p>共 {count} 项</p> : null}
</div>
);
}
// 实际应用示例
function ProductList({ products }) {
return (
<div>
<h2>商品列表</h2>
{/* 错误:products.length为0时显示0 */}
{products.length && <p>共 {products.length} 个商品</p>}
{/* 正确:使用显式比较 */}
{products.length > 0 && <p>共 {products.length} 个商品</p>}
{/* 或使用三元运算符 */}
{products.length > 0 ? (
<p>共 {products.length} 个商品</p>
) : (
<p>暂无商品</p>
)}
</div>
);
}2.2 空字符串的问题
jsx
// ❌ 问题:空字符串是falsy值
function EmptyStringProblem({ text, title }) {
return (
<div>
{/* text为空字符串时不显示 */}
{text && <p>{text}</p>}
{/* 但可能只是想显示空段落 */}
</div>
);
}
// ✅ 解决:检查非空
function EmptyStringSolution({ text }) {
return (
<div>
{/* 检查trim后的长度 */}
{text && text.trim() && <p>{text}</p>}
{/* 或检查不为空字符串 */}
{text !== '' && <p>{text}</p>}
{/* 或使用可选链 */}
{text?.trim() && <p>{text}</p>}
</div>
);
}
// 实际应用
function CommentSection({ comments }) {
return (
<div>
{comments.map(comment => (
<div key={comment.id}>
<p>{comment.author}</p>
{/* 评论内容可能为空,需要检查 */}
{comment.text?.trim() && (
<p className="comment-text">{comment.text}</p>
)}
{/* 回复数量,避免显示0 */}
{comment.replies > 0 && (
<span>{comment.replies} 条回复</span>
)}
</div>
))}
</div>
);
}2.3 NaN和Infinity
jsx
function NaNInfinityHandling() {
const [value, setValue] = useState(NaN);
const [result, setResult] = useState(Infinity);
return (
<div>
{/* NaN是falsy,不会渲染 */}
{value && <p>值:{value}</p>}
{/* Infinity是truthy,会渲染 */}
{Infinity && <p>无限大</p>}
{/* 正确检查NaN */}
{!isNaN(value) && <p>有效值:{value}</p>}
{/* 正确检查有限数 */}
{isFinite(result) && <p>有限值:{result}</p>}
{/* 组合检查 */}
{!isNaN(value) && isFinite(value) && value > 0 && (
<p>正数:{value}</p>
)}
</div>
);
}
// 实际应用:计算器结果显示
function Calculator() {
const [result, setResult] = useState(0);
const divide = (a, b) => {
const res = a / b;
setResult(res);
};
return (
<div>
<button onClick={() => divide(10, 2)}>10 ÷ 2</button>
<button onClick={() => divide(10, 0)}>10 ÷ 0</button>
<div>
结果:
{isFinite(result) ? (
<span>{result}</span>
) : (
<span className="error">无效结果</span>
)}
</div>
</div>
);
}2.4 undefined和null
jsx
function UndefinedNullHandling({ data }) {
return (
<div>
{/* undefined和null都不会渲染 */}
{undefined && <p>不会显示</p>}
{null && <p>不会显示</p>}
{/* 但要小心对象属性 */}
{data && <p>{data.name}</p>} {/* 如果data是null或undefined,不会报错 */}
{/* 使用可选链更安全 */}
{data?.name && <p>{data.name}</p>}
{/* 检查属性是否存在 */}
{data && 'name' in data && data.name && (
<p>{data.name}</p>
)}
</div>
);
}第三部分:逻辑或运算符
3.1 基本用法
jsx
// 语法:value || fallback
// 如果value为falsy,返回fallback
function LogicalOr({ username, title, avatar, bio }) {
return (
<div>
<h1>{title || '默认标题'}</h1>
<p>用户:{username || '访客'}</p>
<img src={avatar || '/default-avatar.png'} alt="头像" />
<p>{bio || '这个人很懒,什么都没写'}</p>
</div>
);
}
// 实际应用:用户资料
function UserProfile({ user }) {
return (
<div className="user-profile">
<h2>{user.name || '匿名用户'}</h2>
<img src={user.avatar || '/default-avatar.png'} alt={user.name} />
<p>{user.bio || '暂无简介'}</p>
<p>邮箱:{user.email || '未设置'}</p>
<p>电话:{user.phone || '未设置'}</p>
<p>地址:{user.address || '未填写'}</p>
</div>
);
}3.2 逻辑或的陷阱
jsx
// ❌ 问题:0和空字符串也会被替换
function OrProblem({ count, price, text }) {
return (
<div>
{/* count为0时,显示"暂无"而不是0 */}
<p>数量:{count || '暂无'}</p>
{/* price为0时,显示"免费"而不是0 */}
<p>价格:{price || '免费'}</p>
{/* text为空字符串时,显示"无内容"而不是空 */}
<p>内容:{text || '无内容'}</p>
</div>
);
}
// ✅ 解决:使用空值合并运算符??
function OrSolution({ count, price, text }) {
return (
<div>
{/* count为0时,显示0 */}
<p>数量:{count ?? '暂无'}</p>
{/* price为0时,显示0(免费) */}
<p>价格:{price === 0 ? '免费' : `¥${price}`}</p>
{/* text为空字符串时,仍显示空字符串 */}
<p>内容:{text ?? '无内容'}</p>
</div>
);
}第四部分:空值合并运算符(??)
4.1 ??运算符详解
jsx
// 语法:value ?? fallback
// 只有value为null或undefined时,返回fallback
// 0、''、false都不会被替换
function NullishCoalescing({ count, text, flag, price }) {
return (
<div>
{/* 0不会被替换 */}
<p>数量:{count ?? '未设置'}</p>
{/* count = 0 → 显示 "数量:0" */}
{/* count = null → 显示 "数量:未设置" */}
{/* 空字符串不会被替换 */}
<p>文本:{text ?? '未设置'}</p>
{/* text = '' → 显示 "文本:" */}
{/* text = null → 显示 "文本:未设置" */}
{/* false不会被替换 */}
<p>标志:{String(flag ?? '未设置')}</p>
{/* flag = false → 显示 "标志:false" */}
{/* flag = null → 显示 "标志:未设置" */}
{/* null和undefined会被替换 */}
<p>{null ?? '默认值'}</p> {/* 显示 "默认值" */}
<p>{undefined ?? '默认值'}</p> {/* 显示 "默认值" */}
</div>
);
}
// 实际应用:产品价格显示
function ProductCard({ product }) {
return (
<div className="product-card">
<h3>{product.name}</h3>
{/* 价格可能为0(免费)或null(未定价) */}
<p className="price">
{product.price === 0
? '免费'
: product.price
? `¥${product.price}`
: '价格待定'}
</p>
{/* 或使用?? */}
<p className="price">
{product.price ?? '价格待定'}
</p>
{/* 库存显示 */}
<p className="stock">
库存:{product.stock ?? '补货中'}
</p>
{/* stock = 0 → "库存:0" */}
{/* stock = null → "库存:补货中" */}
</div>
);
}4.2 ??vs||对比
jsx
function NullishVsOrComparison() {
const values = {
zero: 0,
emptyString: '',
false: false,
null: null,
undefined: undefined
};
return (
<div>
<h3>使用 ||(逻辑或)</h3>
<p>0: {values.zero || '默认'}</p> {/* 默认 */}
<p>空字符串: {values.emptyString || '默认'}</p> {/* 默认 */}
<p>false: {String(values.false || '默认')}</p> {/* 默认 */}
<p>null: {values.null || '默认'}</p> {/* 默认 */}
<p>undefined: {values.undefined || '默认'}</p> {/* 默认 */}
<h3>使用 ??(空值合并)</h3>
<p>0: {values.zero ?? '默认'}</p> {/* 0 */}
<p>空字符串: {values.emptyString ?? '默认'}</p> {/* '' */}
<p>false: {String(values.false ?? '默认')}</p> {/* false */}
<p>null: {values.null ?? '默认'}</p> {/* 默认 */}
<p>undefined: {values.undefined ?? '默认'}</p> {/* 默认 */}
</div>
);
}
// React 19推荐使用??
function React19Recommendation({ data }) {
return (
<div>
{/* ✅ 推荐:使用?? */}
<p>数量:{data.count ?? 0}</p>
<p>名称:{data.name ?? '未命名'}</p>
<p>价格:{data.price ?? '待定'}</p>
{/* ❌ 不推荐:使用||(除非确实要过滤falsy值) */}
<p>数量:{data.count || 0}</p>
{/* 如果count为0,会显示0(符合预期) */}
{/* 但如果想区分0和null/undefined,就有问题 */}
</div>
);
}第五部分:可选链运算符(?.)
5.1 基本用法
jsx
// 语法:object?.property
// 如果object是null或undefined,返回undefined
// 否则返回object.property
function OptionalChaining({ user }) {
return (
<div>
{/* 传统方式:需要逐层检查 */}
{user && user.profile && user.profile.avatar && (
<img src={user.profile.avatar} alt="头像" />
)}
{/* 使用可选链:简洁明了 */}
{user?.profile?.avatar && (
<img src={user.profile.avatar} alt="头像" />
)}
{/* 数组可选链 */}
{user?.friends?.[0]?.name && (
<p>第一个好友:{user.friends[0].name}</p>
)}
{/* 函数可选链 */}
{user?.getFullName?.() && (
<p>全名:{user.getFullName()}</p>
)}
</div>
);
}
// 实际应用:复杂数据展示
function UserDashboard({ userData }) {
return (
<div className="dashboard">
<h2>{userData?.user?.name ?? '访客'}</h2>
{/* 安全访问嵌套属性 */}
{userData?.user?.profile?.bio && (
<p>{userData.user.profile.bio}</p>
)}
{/* 数组长度检查 */}
{(userData?.posts?.length ?? 0) > 0 && (
<div>
<h3>文章 ({userData.posts.length})</h3>
{userData.posts.map(post => (
<article key={post.id}>
<h4>{post.title}</h4>
{post?.content?.substring(0, 100)}...
</article>
))}
</div>
)}
{/* 方法调用 */}
{userData?.user?.getPremiumStatus?.() && (
<span className="premium">VIP会员</span>
)}
</div>
);
}5.2 可选链与空值合并组合
jsx
function CombinedOperators({ data }) {
return (
<div>
{/* 结合使用?.和?? */}
<p>名称:{data?.user?.name ?? '未命名'}</p>
<p>年龄:{data?.user?.age ?? '未知'}</p>
<p>邮箱:{data?.user?.email ?? '未设置'}</p>
{/* 先用?.安全访问,再用??提供默认值 */}
<p>城市:{data?.user?.address?.city ?? '未知城市'}</p>
{/* 数组元素访问 */}
<p>第一项:{data?.items?.[0]?.name ?? '无数据'}</p>
{/* 方法调用结果 */}
<p>状态:{data?.getStatus?.() ?? '未知'}</p>
</div>
);
}第六部分:组合使用模式
6.1 多个&&组合
jsx
function MultipleAndConditions({ user, isPremium, hasAccess, isVerified }) {
return (
<div>
{/* 所有条件都满足才显示 */}
{user && isPremium && hasAccess && (
<PremiumContent />
)}
{/* 链式判断对象属性 */}
{user && user.profile && user.profile.avatar && (
<img src={user.profile.avatar} alt="头像" />
)}
{/* 使用可选链简化 */}
{user?.profile?.avatar && (
<img src={user.profile.avatar} alt="头像" />
)}
{/* 复杂条件 */}
{user &&
isPremium &&
isVerified &&
user.credits > 100 && (
<VIPFeatures />
)}
{/* 条件函数 */}
{user &&
checkPremiumStatus(user) &&
checkAccessRights(user) && (
<SpecialContent />
)}
</div>
);
}6.2 &&和??组合
jsx
function CombineAndNullish({ user, config, defaultTheme }) {
return (
<div>
{/* 先判断存在,再提供默认值 */}
{user && (
<h2>{user.name ?? '匿名用户'}</h2>
)}
{/* 链式使用 */}
<p>
邮箱:{user && (user.email ?? '未设置')}
</p>
{/* 或使用可选链 */}
<p>
电话:{user?.phone ?? '未设置'}
</p>
{/* 配置值处理 */}
{config && (
<div className={config.theme ?? defaultTheme}>
主题:{config.theme ?? '默认主题'}
</div>
)}
{/* 数组元素 */}
{user?.friends && (
<p>
好友数:{user.friends.length ?? 0}
</p>
)}
</div>
);
}6.3 &&和三元运算符组合
jsx
function CombineAndTernary({ user, status }) {
return (
<div>
{/* 简单条件用&& */}
{user && <WelcomeMessage name={user.name} />}
{/* 复杂条件用三元 */}
{user ? (
user.isPremium ? (
<PremiumDashboard />
) : (
<FreeDashboard />
)
) : (
<Login />
)}
{/* 组合使用 */}
{status === 'loading' ? (
<Spinner />
) : status === 'error' ? (
<ErrorMessage />
) : user && (
<UserContent user={user} />
)}
</div>
);
}第七部分:实战案例
7.1 案例1:条件导航菜单
jsx
function ConditionalNav({ user, permissions }) {
const canViewDashboard = user && permissions.includes('dashboard');
const canManageUsers = user && permissions.includes('manage_users');
const canViewReports = user && permissions.includes('reports');
const canAccessSettings = user && permissions.includes('settings');
return (
<nav>
{/* 总是显示 */}
<a href="/">首页</a>
{/* 登录后显示 */}
{user && <a href="/profile">个人资料</a>}
{/* 有权限才显示 */}
{canViewDashboard && <a href="/dashboard">仪表板</a>}
{canManageUsers && <a href="/users">用户管理</a>}
{canViewReports && <a href="/reports">报表</a>}
{canAccessSettings && <a href="/settings">设置</a>}
{/* VIP功能 */}
{user?.isPremium && <a href="/premium">VIP专区</a>}
{/* 管理员功能 */}
{user?.role === 'admin' && <a href="/admin">管理后台</a>}
{/* 登录/退出 */}
{user ? (
<button onClick={logout}>退出</button>
) : (
<a href="/login">登录</a>
)}
</nav>
);
}7.2 案例2:数据加载状态
jsx
function DataDisplay({ loading, error, data }) {
return (
<div>
{/* 加载状态 */}
{loading && (
<div className="loading">
<Spinner />
<p>加载中...</p>
</div>
)}
{/* 错误状态 */}
{error && (
<div className="error">
<p>加载失败:{error.message}</p>
<button onClick={retry}>重试</button>
</div>
)}
{/* 数据显示 */}
{!loading && !error && data && (
<div className="content">
{data.length > 0 ? (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
) : (
<p className="empty">暂无数据</p>
)}
</div>
)}
{/* 或使用更清晰的条件 */}
{!loading && !error && data?.length === 0 && (
<EmptyState />
)}
</div>
);
}7.3 案例3:功能开关
jsx
function FeatureFlags({ features }) {
return (
<div>
{/* 新功能开关 */}
{features.enableNewUI && (
<NewUIComponent />
)}
{/* 实验性功能 */}
{features.experimental && (
<ExperimentalFeature />
)}
{/* A/B测试 */}
{features.variant === 'A' ? (
<VariantA />
) : features.variant === 'B' ? (
<VariantB />
) : (
<DefaultVariant />
)}
{/* 组合条件 */}
{features.enableChat && features.isPremium && (
<PremiumChatFeature />
)}
</div>
);
}第八部分:最佳实践
8.1 选择合适的运算符
jsx
function OperatorSelection() {
// ✅ 场景1:单一条件,无else → 使用&&
{isLoggedIn && <WelcomeMessage />}
// ✅ 场景2:二选一 → 使用三元运算符
{isLoggedIn ? <Dashboard /> : <Login />}
// ✅ 场景3:提供默认值(排除null/undefined)→ 使用??
<p>{username ?? '访客'}</p>
// ✅ 场景4:提供默认值(排除所有falsy)→ 使用||
<p>{title || '无标题'}</p>
// ✅ 场景5:安全访问嵌套属性 → 使用?.
{user?.profile?.avatar && <img src={user.profile.avatar} />}
// ✅ 场景6:多条件 → 提前返回或使用函数
function renderContent() {
if (loading) return <Spinner />;
if (error) return <Error />;
if (!data) return <Empty />;
return <Content data={data} />;
}
return <div>{renderContent()}</div>;
}8.2 避免复杂嵌套
jsx
// ❌ 不好:复杂的嵌套条件
function BadNesting({ user, loading, error }) {
return (
<div>
{loading ? (
<Spinner />
) : error ? (
<Error />
) : user ? (
user.isPremium ? (
user.isActive ? (
<PremiumDashboard />
) : (
<InactiveNotice />
)
) : (
user.trialExpired ? (
<TrialExpired />
) : (
<FreeDashboard />
)
)
) : (
<Login />
)}
</div>
);
}
// ✅ 好:提前返回
function GoodEarlyReturn({ user, loading, error }) {
if (loading) return <Spinner />;
if (error) return <Error />;
if (!user) return <Login />;
if (!user.isActive) return <InactiveNotice />;
if (user.isPremium) return <PremiumDashboard />;
if (user.trialExpired) return <TrialExpired />;
return <FreeDashboard />;
}
// ✅ 好:提取为独立组件
function GoodComponentSplit({ user, loading, error }) {
function renderContent() {
if (loading) return <Spinner />;
if (error) return <Error />;
if (!user) return <Login />;
return <UserDashboard user={user} />;
}
return <div>{renderContent()}</div>;
}
function UserDashboard({ user }) {
if (!user.isActive) return <InactiveNotice />;
if (user.isPremium) return <PremiumDashboard />;
if (user.trialExpired) return <TrialExpired />;
return <FreeDashboard />;
}8.3 性能考虑
jsx
function PerformanceConsiderations() {
const [showExpensive, setShowExpensive] = useState(false);
// ❌ 不好:即使不显示也会计算
const expensiveValue = expensiveCalculation();
return (
<div>
{showExpensive && <ExpensiveComponent value={expensiveValue} />}
</div>
);
// ✅ 好:使用useMemo延迟计算
const expensiveValue = useMemo(() => {
if (!showExpensive) return null;
return expensiveCalculation();
}, [showExpensive]);
return (
<div>
{showExpensive && <ExpensiveComponent value={expensiveValue} />}
</div>
);
// ✅ 或者:直接在条件内计算
return (
<div>
{showExpensive && <ExpensiveComponent value={expensiveCalculation()} />}
</div>
);
}练习题
基础练习
- 使用&&实现条件渲染
- 避免0和空字符串的渲染问题
- 使用??提供默认值
- 使用?.安全访问嵌套属性
进阶练习
- 对比||、??和三元运算符的区别
- 实现复杂的条件组合渲染
- 重构嵌套的条件语句
- 优化条件渲染的性能
高级练习
- 实现一个权限控制系统,使用各种条件运算符
- 创建一个智能表单,根据条件显示/隐藏字段
- 实现功能开关系统(Feature Flags)
通过本章学习,你已经掌握了逻辑运算符在React中的完整应用。选择合适的运算符能让你的代码更加简洁、安全、高效。继续学习,成为React渲染专家!