ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

React条件渲染全指南:从三元运算符到对象映射的选型与优化

React条件渲染全指南:从三元运算符到对象映射的选型与优化

1. 条件渲染这件事,先建立正确的全局认知

我在实际开发React组件时,条件渲染几乎出现在每一个业务页面里。登录后显示用户信息、未登录时显示登录按钮,后台管理里根据不同角色渲染不同的操作按钮,多步骤表单里根据当前步骤渲染对应区块……这些都是条件渲染。本质上,React的条件渲染就是“在渲染逻辑中根据某个状态或变量,决定返回哪些JSX”。而一个开发者对这个看似简单的问题处理得好不好,往往直接决定了代码的可维护性和组件的健壮性。很多新人写的第一版组件功能没问题,但需求一变化,改起来非常痛苦,问题多半就出在当初条件渲染的方式没选对。

这篇内容适合正在学习React的初学者,也适合已经能写业务组件、但想系统梳理条件渲染方式并避开常见坑的中级开发者。我写这篇东西的动机,是自己这几年在多个项目里踩过条件渲染的坑,以及帮同事review代码时反复看到类似的写法问题,所以把这些经验完整整理出来,可以作为一份直接参考的实践手册。

先提一个最基础的规律:条件渲染方式的选择,核心看两件事——条件分支的数量,以及分支逻辑的复杂度。后面所有方法的讨论、选型建议,都是围绕这两点展开的。另外,条件渲染不只是“挂载或不挂载”,它还涉及组件状态保留、性能开销、用户交互体验等一连串问题。如果你仅仅把它理解成if的语法变形,那后面这些坑迟早会踩上。

2. 主流写法拆解:各自适合什么场景

2.1 三元运算符:最常用的双分支方案

三元运算符大概是日常开发里出现频率最高的条件渲染写法。它语法简单,内联在JSX里几乎不需要额外拆结构,非常适合“满足条件渲染A,否则渲染B”这种双分支场景。

function UserGreeting({ user }) { return ( <div> {user ? <WelcomePanel user={user} /> : <LoginButton />} </div> ); }

这段代码从可读性上讲没有问题,两个分支都不复杂,一眼能看明白。但我建议你记住一条红线:当三元表达式某个分支里的JSX超过三行时,就应该考虑提取变量或函数。原因很简单,JSX本身就是一种描述性语法,一旦嵌套层级变多,括号匹配和缩进会迅速消耗人的阅读精力,特别是后期维护的时候。

我review项目时见过类似这样的代码:

return ( <Card> {isAdmin ? <AdminToolbar onExport={handleExport} onImport={handleImport} onClearCache={handleClearCache} /> : <UserToolbar onRefresh={handleRefresh} />} </Card> );

到这一步还算可控,但如果AdminToolbar的props继续增加,两个分支又各自嵌套其他JSX,实际写出来会非常难看。我的建议是:分支内元素数量超过两个,或者props超过三五个,先把节点提取出来。

const toolbar = isAdmin ? ( <AdminToolbar onExport={handleExport} onImport={handleImport} /> ) : ( <UserToolbar onRefresh={handleRefresh} /> ); return <Card>{toolbar}</Card>;

提取后return里的JSX变简洁了,分支逻辑也被隔离到更聚焦的区域。三元运算符最大的好处是内联、直接、不打断JSX的阅读流,但它的边界也很明显——一旦分支内部再出现条件嵌套,就成了“三元地狱”。我见过有人写四层嵌套的三元运算,最后自己都分不清哪个分支对应哪个条件,这种代码基本只能重写。遇到两个以上条件关联的情况,应该及时转向更结构化的写法。

2.2 逻辑与(&&)的隐藏陷阱:为什么0会显示在页面上

逻辑与是另一种高频写法,常用于“当某条件满足时,渲染某个元素;条件不满足时什么都不渲染”。

{isLoggedIn && <Dashboard />}

这种写法非常简洁,在React中也很常见。但我必须提醒一个无数人踩过的坑:当条件左侧是数字0时,页面会把0渲染出来。

function CartBadge({ count }) { return ( <div> {count && <span>你有 {count} 件商品</span>} </div> ); }

当count为0时,表达式的结果是0,而不是false或null。JavaScript里0是假值,所以判断逻辑没问题,问题出在React渲染时。React在渲染时会把false、null、undefined视为“什么都不渲染”,但不会忽略数字0。于是页面上就会莫名其妙出现一个“0”。

正确写法是加一步布尔转换:

{count > 0 && <span>你有 {count} 件商品</span>}

或者使用!!count强制转成布尔值:

{!!count && <span>你有 {count} 件商品</span>}

我推荐count > 0这种写法,因为它在语义上更准确,明确表达了“当数量大于0时”的意图。顺便说一句,这也是我在Code Review时最常见到的问题之一,几乎每个团队的新人都被这个0坑过。还有个变体是users.length,当数组为空时同样会渲染出0。排查方法很简单——在浏览器Elements面板里查看是否有孤零零的文本节点。团队规范里可以直接加一条:“逻辑与左侧必须是布尔表达式,禁止直接放数字或数组长度”。

2.3 if/else 提前返回:多分支方案的最佳可读性选择

当条件逻辑不只是简单的布尔判断,而是涉及多个状态、多个中间变量的计算时,再强行用三元或逻辑与会把JSX弄得很拧巴。这时候最自然也最可读的方式,是回到JavaScript最原始的if判断。

React函数组件里,你完全可以在return之前用if判断,决定返回的内容:

function OrderPage({ order }) { if (order.status === 'pending') { return <PendingProgress order={order} />; } if (order.status === 'paid') { return <PaidDetail order={order} />; } if (order.status === 'refunded') { return <RefundDetail order={order} />; } return <UnknownStatus order={order} />; }

这是React官方文档也明确支持的逻辑——组件本质上就是一个返回JSX的函数,函数体内当然可以执行任意JavaScript逻辑。这种写法在处理超过两个状态分支时,可读性几乎是无敌的。每个if都提前返回,不需要考虑后续的else归属,也不会出现括号匹配错误,逻辑路线非常清楚。

还有一部分开发者喜欢在JSX里写立即执行函数,比如:

return ( <div> {(() => { if (plan === 'free') return <FreeBadge />; if (plan === 'pro') return <ProBadge />; return <EnterpriseBadge />; })()} </div> );

这种写法在功能上没问题,也能解决多分支问题。但说实话,我不太推荐。立即执行函数出现在JSX中间,把渲染和逻辑混在一起,如果函数体再写长一点,整个return区域的可读性会快速下降。如果你有这样的分支逻辑,不如提到组件顶部,用if提前return,或者干脆提取成一个独立组件。JSX应该尽量保持“描述界面结构”的本职,而不是承载复杂流程控制。

2.4 对象映射与 switch:管理大量独立分支的优雅方案

当分支数量超过三个,且每个分支对应的组件或结构相对独立时,对象映射是非常优雅的方案。核心思想是利用key -> value的映射关系,把“状态到界面的对应关系”用数据描述出来,界面代码只负责查表。

const VIEW_MAP = { draft: <DraftView />, publishing: <PublishingView />, published: <PublishedView />, archived: <ArchivedView />, }; function ArticleViewer({ status }) { if (!status) return null; return VIEW_MAP[status] || <UnknownView />; }

注意一个细节:VIEW_MAP里的值如果直接写<DraftView />,那么这些组件在函数组件每次渲染时都会被创建(调用createElement),即使最终没被返回,也会产生不必要的元素创建开销。虽然React相比直接渲染来说开销很小,但在某些对性能敏感的场景,我推荐把映射值写成组件引用,而不是元素:

const VIEW_MAP = { draft: DraftView, publishing: PublishingView, published: PublishedView, archived: ArchivedView, }; function ArticleViewer({ status }) { const ViewComp = VIEW_MAP[status]; if (!ViewComp) return null; return <ViewComp />; }

通过ViewComp作为动态组件来渲染,只有当状态命中时,才会创建对应的元素。这样既保证了多分支查找的效率,又避免了无效的元素开销。这种模式在tab切换、多状态页面审核等场景里非常实用。

如果多个状态分支的处理逻辑和渲染内容都比较类似,只是某个参数不同,那也可以用switch:

function getButtonMeta(status) { switch (status) { case 'todo': return { text: '开始', theme: 'primary', action: startHandler }; case 'doing': return { text: '进行中', theme: 'warning', action: null }; case 'done': return { text: '已完成', theme: 'default', action: null }; default: return { text: '未知', theme: 'default', action: null }; } }

实际项目中,我使用对象映射的频率远高于switch,因为对象映射的查找开销更低,写法也更符合React数据驱动视图的思维方式。switch更适合“集中定义元信息”的场景——比如根据状态返回一组配置值,再由界面统一消费。

3. 三个实战场景:方案不是空谈,而是解决问题

方法论看多了容易飘,这一节我用三个从实际项目里抽象出来的场景,完整拆解每种方案在真实需求中的定位。

3.1 权限控制场景:角色不同,操作区不同

任何后台管理系统,几乎都离不开基于用户角色的操作区渲染。常见的需求是:管理员能看到“导入、导出、升级、管理”等操作,普通用户只能看到“导出”,访客则只看到一个置灰的提示状态。

用对象映射来做非常合适:

const ROLE_ACTIONS = { admin: ['export', 'import', 'upgrade', 'manage'], user: ['export'], guest: [], }; function ActionZone({ role }) { const actions = ROLE_ACTIONS[role] || []; if (actions.length === 0) { return <EmptyPermissionNotice />; } return ( <div className="action-zone"> {actions.includes('export') && <ExportButton />} {actions.includes('import') && <ImportButton />} {actions.includes('upgrade') && <UpgradeButton />} {actions.includes('manage') && <ManageButton />} </div> ); }

这个方案的核心优势在于,权限配置被集中在一个对象里,而不是散落在各个if判断中。如果产品经理说“管理员还要增加一个邀请功能”,你只需要在ROLE_ACTIONS里加一个字符串,再在渲染处加一行{actions.includes('invite') && <InviteButton />},整体逻辑高度可维护。

如果你的权限规则更复杂,可以把配置从字符串数组升级为对象数组,每个元素包含权限key、对应组件、排序优先级等字段。核心思想始终是“数据驱动渲染”——界面代码向配置表查询结果,而不是自己处理一堆分支。这样做还有个额外好处:配置结构清晰后,后端返回的权限编码可以直接映射到这个表上,前端不需要再散落地翻译角色与权限的关系。

3.2 多步骤表单场景:当前步骤决定渲染哪一块

多步骤表单是条件渲染另一个典型场景,常见做法是用currentStep作为当前状态,渲染对应步骤组件,并提供上一步/下一步的切换能力。

function CheckoutForm() { const [step, setStep] = useState(1); const renderStep = () => { if (step === 1) return <AddressStep />; if (step === 2) return <PaymentStep />; if (step === 3) return <ConfirmStep />; return null; }; return ( <div className="checkout-form"> {renderStep()} <div className="form-actions"> {step > 1 && <button onClick={() => setStep(step - 1)}>上一步</button>} {step < 3 && <button onClick={() => setStep(step + 1)}>下一步</button>} {step === 3 && <button onClick={submitOrder}>提交订单</button>} </div> </div> ); }

这里有两个小细节值得展开。第一,{step > 1 && ...}这类条件直接用比较表达式,而不是{step !== 1 && ...}。原因在于范围比较在语义上更安全——比如产品经理后续把步骤增加到4步,step > 1依旧成立,不需要修改。第二,当前步骤组件在切换时会被卸载再挂载,表单内部已填写的内容会丢失。如果你需要跨步骤保留草稿数据,最好把表单数据提升到父组件统一管理,而不是依赖子组件内部自带的useState。

这个场景还有个常见问题:用户填完第一步,切到第二步时发现第一步的数据还在,这是好事;但切回第一步时发现数据没了,这就说明数据放在步骤子组件内部了。解决方式是把address、payment、confirm等数据统一提升到CheckoutForm中,各步骤组件改为受控组件,通过props接收和提交数据。

对于步骤特别多、且需要扩展步骤的情况,用对象映射会更好。比如把步骤名作为key,组件作为value,currentStep只需对应查表。这种模式在配置类向导应用中非常实用,因为新增一个步骤不需要改动渲染逻辑,只在映射表里添加新条目。

3.3 路由级别条件渲染:登录态与访问控制

除了组件内部的条件渲染,另一个常见场景是路由级别。在React Router中,我们希望未登录用户访问受保护页面时被重定向到登录页。

function ProtectedRoute({ isLogin, children }) { if (!isLogin) { return <Navigate to="/login" replace />; } return children; }

使用方式:

<Route path="/dashboard" element={<ProtectedRoute isLogin={isLogin}><Dashboard /></ProtectedRoute>} />

当登录状态从true变为false时,ProtectedRoute会卸载内部的所有子树并触发重定向,实现“会话失效立即踢出”的效果。这种条件渲染的本质,是在渲染入口处提前拦截分支,而不是让具体页面判断自己的可见性。

如果你的鉴权逻辑更细,比如某些页面需要管理员角色,可以在ProtectedRoute里增加roles参数:

function RoleRoute({ user, roles, children }) { if (!user) { return <Navigate to="/login" replace />; } if (roles && !roles.includes(user.role)) { return <Navigate to="/403" replace />; } return children; }

但要提醒一句:前端路由守卫只是体验层面的拦截,真正的权限校验必须依赖后端接口实现。前端控制决定“用户看不到某些页面”,后端控制决定“用户不能调某些接口”。两者要配合使用,不能只靠前端。

4. 条件渲染不只是“挂不挂”的问题,性能和体验要一起考虑

很多开发者刚接触React时,容易把条件渲染简单理解为“条件满足就挂载,不满足就不挂载”。从结果看确实如此,但从工程角度看,选择挂载/卸载还是隐藏,会直接影响组件状态、网络请求和用户体验。

4.1 挂载与隐藏的取舍,基于状态和请求的需求判断

假设一个详情页面有一个“高级分析模块”,普通用户不需要看到。你可以有两种做法:

  • 条件渲染时完全不渲染,即{showAdvanced && <AdvancedAnalysis />}
  • 始终渲染DOM,但用CSS控制显示/隐藏,即<div className={showAdvanced ? 'visible' : 'hidden'}><AdvancedAnalysis /></div>

两者的差别很关键:

  • 前者会在条件不满足时卸载子组件,子组件里的所有useEffect、useState都会被清理,下次显示时重新初始化。这意味着如果AdvancedAnalysis内部需要请求接口,每次打开都会重新拉取数据。
  • 后者始终保持组件挂载,子组件状态不会丢失,切换不会触发重新请求,但会一直占用内存和DOM节点。

我的实践建议是:

  • 如果子组件内部有过程性状态,比如用户填到一半的草稿、滚动位置、临时筛选条件,应当保留挂载,用CSS隐藏。
  • 如果子组件内容是展示型数据,且每次展示都要最新数据,或者你不希望它在后台悄悄加载,那就用条件渲染挂载/卸载。
  • 如果子组件挂载时有重计算或大体积资源,且使用频率低,可以考虑用动态导入加条件渲染结合,按需加载。
const AdvancedAnalysis = React.lazy(() => import('./AdvancedAnalysis')); function Page({ showAdvanced }) { return ( <div> {showAdvanced && ( <Suspense fallback={<div>模块加载中...</div>}> <AdvancedAnalysis /> </Suspense> )} </div> ); }

这种“条件渲染 + 动态导入”的组合,是很实用的优化手段,尤其适合那些重而不常用的区块。比如后台系统里的“数据报表”模块,用户可能一周才打开一次,但打包体积占了整个应用的30%。用上动态导入后,首屏体积能明显降下来,用户首次访问速度和最终打开报表时的加载体验都会有所改善。

4.2 减少无效渲染:提取组件、缓存、拆分

条件渲染本身不复杂,但它们组合起来,往往会造成性能浪费。举个例子,一个大型列表组件里有一个筛选状态,筛选条件下方才显示某行按钮。如果每次点击筛选按钮,整个列表都重新渲染,开销会比较大。优化的核心思路有几个层面。

第一层:把条件逻辑和条件渲染的部分下沉到小的子组件,让状态变化的粒度最小化。比如每行的按钮显示逻辑,拆到独立RowAction组件里,这样父列表重渲染时,子组件可以通过React.memo减少不必要的更新。

第二层:如果条件依赖某个计算量较大的值,比如字符串处理、数组过滤,在外层用useMemo缓存计算结果,避免每次渲染都重复计算:

const visibleItems = useMemo(() => { return list.filter(item => item.status === filterStatus); }, [list, filterStatus]);

第三层:条件渲染和高频更新场景——比如输入框每敲一个字符触发一次重渲染——要特别注意是否把本来无需更新的区块也连带渲染了。常见做法是拆分受控组件,或者用React.memo包裹不需要频繁更新的子组件:

const HeavyCommentList = React.memo(function HeavyCommentList({ comments }) { return <ul>{comments.map(...)}</ul>; });

记住一点:React.memo不是万能的,它只能做浅比较。如果父组件往下传的对象每次都是新引用,即使内容相同,memo也拦截不住重渲染。更根本的思路,是让条件渲染和状态分布尽量贴合业务边界,从源头减少不必要的更新范围。比如把筛选状态放在父组件,但把列表本身抽成稳定的子组件,这样状态变化只影响需要变更的区域。

4.3 列表中的条件分支:key与组件形态一致性

在列表渲染里,经常会出现每一行根据状态渲染不同内容的场景。比如订单列表,已完成订单行显示“再来一单”按钮,已取消订单行显示“删除记录”按钮。这里容易踩的坑是:不同状态的行渲染的是完全不同的JSX结构,却用了相同的key选取逻辑,导致React的diff算法产生误判。

{orders.map(order => { return ( <tr key={order.id}> <td>{order.title}</td> <td> {order.status === 'done' && <ActionButton label="再来一单" />} {order.status === 'cancelled' && <ActionButton label="删除记录" />} {order.status === 'pending' && <ActionButton label="查看详情" />} </td> </tr> ); })}

这段代码简单看没什么问题。但注意,如果三个ActionButton内部使用了不同的props组合,React在比较前后两次渲染时,可能会复用组件状态,导致按钮状态残留。遇到这种“同位置不同组件类型”的条件渲染,我倾向于提取成一个独立的StatusAction组件,让OrderRow的结构保持稳定:

function OrderRow({ order }) { return <StatusAction status={order.status} orderId={order.id} />; } function StatusAction({ status, orderId }) { if (status === 'done') return <ActionButton label="再来一单" orderId={orderId} />; if (status === 'cancelled') return <ActionButton label="删除记录" orderId={orderId} />; if (status === 'pending') return <ActionButton label="查看详情" orderId={orderId} />; return null; }

关键点是:状态变化时,React需要能准确识别组件类型的切换。把条件分支封装进独立组件后,OrderRow内部始终只渲染一个StatusAction,组件类型一致,React只会更新props,不做额外的卸载挂载,性能更稳定,状态也不容易错乱。

5. 常见问题与排查技巧实录

5.1 列表更新后,条件渲染的按钮状态却残留

这个现象在表格数据刷新时尤其常见。用户点击某个按钮后,按钮变成“已处理”状态,但列表刷新后,其他行竟然也出现了“已处理”。我排查过这种问题,最终定位到原因:多个状态分支渲染的是同一个ActionButton组件,React在更新时复用了组件实例,旧的props状态没有完全重置。解法是前面说的提取独立条件组件,让每组状态都有清晰的组件边界,必要时用key区分不同分支实例。

5.2 切换分支后,子组件的输入框内容还在

还有一种情况:在A、B两个组件间切换,B组件里有输入框,切走再切回来,输入框内容居然还在。很多人以为是bug,其实这是React在复用组件实例。因为条件渲染的位置和组件类型相同,React在diff时认为“同一个组件树节点”,只是更新了props,所以不会彻底卸载。

如果你希望每次切换都重新初始化子组件,有一个特别实用的办法——给组件加key:

{showA ? <StepPanel key="a" model="A" /> : <StepPanel key="b" model="B" />}

只要showA变化,key就从a变成b,React会认为这是两个不同的组件实例,强制卸载重新挂载。这个技巧在表单步骤、Tab切换、不同数据源的编辑页面里非常实用。反过来,如果你希望保留状态,那就不该用key强制区分,而应把状态提升到父组件,或保持组件一直挂载、用CSS隐藏避免卸载。

5.3 异步数据返回后,条件渲染没有更新

典型场景是:组件挂载时发请求,请求返回后设置某个状态,这个状态控制某段JSX的渲染。有时候你会发现请求已经返回了,但界面还是旧的。问题多半不在条件渲染本身,而在异步回调中使用了旧的闭包状态。

function Profile({ userId }) { const [profile, setProfile] = useState(null); useEffect(() => { fetchProfile(userId).then(data => setProfile(data)); }, [userId]); if (!profile) return <Loading />; return <ProfileCard profile={profile} />; }

如果userId变化但依赖数组漏了userId,或者闭包中捕获了旧的userId,切换后组件会先用旧状态渲染,后台数据才慢慢更新。排查时先检查useEffect的依赖数组是否完整,同时用标志位防止竞态。更稳妥的写法是给异步请求加取消机制,比如用AbortController。这样用户快速切换多个ID时,旧请求不会覆盖新结果。

5.4 条件渲染导致的表单受控状态失效

另一个常见问题出现在表单元素上。某些条件渲染逻辑会在用户输入过程中触发组件重渲染,导致输入框突然丢失焦点,或者内容变成受控/非受控混合状态。这类问题通常不是因为条件渲染本身,而是因为条件分支里在不恰当的位置改变了输入框的value或defaultValue属性类型。排查时重点检查:同一输入框是否在某个分支里受控(有value),在另一个分支里非受控(没value)。保持两种模式一致,能避免大量奇怪问题。

5.5 条件渲染在严格模式下的双请求问题

React的严格模式在开发环境下会故意多次执行某些函数,用来暴露副作用问题。如果你的组件内部有发送请求的useEffect,且依赖数组里包含条件判断的状态,严格模式下可能会看到请求被重复发送。这通常不是条件渲染的锅,但会让前来排查问题的同事误以为条件渲染写错了。我的经验是:不要把请求逻辑直接挂在条件判断结果上,而是把请求放在副作用里,并把依赖数组写完整。这样无论条件如何变化,请求的触发路径只有一个。

6. 一套适合直接套用的条件渲染决策思路

写了这么多,最后分享一些更接近“方案决策”的经验,你可以把它当作一个快速的核查清单。

6.1 按分支数量和复杂度快速选型

场景推荐方案说明
双分支,条件简单三元运算符内联简洁,分支不长
单条件,不满足则无逻辑与(&&)注意左侧写布尔表达式
3到5个分支,相互独立if 提前 return逻辑清晰,容易扩展
5个以上分支,状态到组件对象映射数据驱动,集中管理
需要匹配多个值并返回配置switch适合集中定义元信息
需要保留子组件状态CSS隐藏不卸载,保留挂载状态
按需加载重模块动态导入+条件渲染减少首屏体积

这张表不是绝对的,但它覆盖了我在实际项目中最常用的选型路径。选型时还有一个额外判断维度:这个逻辑未来会不会扩展。如果产品说“这周只做两个角色”,但你知道下周会加到六个角色,那就别图省事写三元,直接用对象映射或提前返工的分支结构,后续扩展成本会低很多。

6.2 条件变量的放置位置,影响渲染的稳定性

这里有一个非常实用但容易被忽略的原则:条件变量的状态放置位置,会影响条件渲染的稳定性和性能。如果条件状态是某个组件内部的useState,这个组件的每次重渲染都会重新估算条件。如果条件状态来自全局store,那么store更新引发的任何重渲染,都会流过这个组件。

换句话说,不要把所有条件状态都放在顶层组件里,也不要全都塞进局部组件。具体做法是:把只影响局部区块的条件状态放到离它最近的组件里;把影响多个子组件的共享状态放到上层,再用memo或useMemo控制下层不必要的重渲染。条件渲染和状态管理本来就是配套设计,一起考虑,修bug时能省不少时间。

6.3 做一次“代码气味”检查

每次写完条件渲染,我会刻意回头做一次代码气味检查,核心问题就这几个:

  • 这个条件表达式放在JSX里,嵌套有没有超过三层?
  • 如果一个分支里出现两个以上重复的组件或props,是不是该提取一个映射配置?
  • 会不会因为条件渲染导致某个共用组件在多个分支间被复用,却出现了状态残留?
  • 分支里的组件是否真的需要每次卸载,还是应该用CSS控制显示?

这些问题一过,基本能把80%的潜在隐患提前消灭掉。我曾经在某个数据后台项目里,把散落在各个组件里的tab权限判断收敛到一个统一的映射表里,后期新增两个角色只花了不到半小时,而之前每次改角色权限都要翻好几个文件。这种收益,靠的不是某个高级语法,而是最初选择了与业务结构匹配的条件渲染模式。

6.4 一点写在最后的个人体会

做React开发这几年,我有一个越来越强烈的体会:条件渲染的方式选择,往往反映一个人对组件边界和业务结构的理解深度。同样的页面,有人写出来全是嵌套三元,有人写出来是一张清晰的状态映射。差别不在于谁更会写JSX,而在于谁在动手之前先想清楚了“状态从哪来、分支怎么长”。因此我给你的最终建议是:不要一开始就盯着具体语法,先把业务的状态枚举列出来,再看每个状态对应的界面长什么样,最后再回头选写法。顺序对了,方案自然不会差。

往后你在项目里如果遇到条件渲染相关的报错或状态残留问题,再翻回这篇记一记,对照里面的场景和排查清单,应该能找到对应的答案。条件渲染不难,真正难的是每次在多个方案里做出最适合当前业务的取舍,这种判断力只能靠多写、多复盘、多review来积累。下次聊组件设计的时候,再把状态管理和条件渲染放在一起深挖。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进