尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

别再这样写条件渲染了——你的React组件里藏着这5种定时炸弹

别再这样写条件渲染了——你的React组件里藏着这5种定时炸弹 你的React组件里一定有条件渲染。{condition Component /}、三元表达式、if/else——这些你每天都在写。但这些写法里有5种坑不出问题的时候一切正常一旦触发就是线上事故。我在生产环境里被炸了不止一次。炸弹1 短路渲染出一个0// ❌ 你以为它不会渲染但它渲染了一个0functionNotificationBadge({count}){return(div{countspanclassNamebadge{count}/span}/div);}// 当 count0时页面上会出现一个孤零零的0为什么的短路规则左侧是 falsy 就返回左侧的值。0是 falsy所以返回0。React 会把数字0渲染到 DOM 里。同样的坑还有空字符串——不会渲染但NaN会渲染出NaN。// ✅ 显式转为布尔值{count0spanclassNamebadge{count}/span}// 或者用三元{count ?spanclassNamebadge{count}/span:null}// 通用安全写法!!{!!items.lengthListitems{items}/}原则左侧必须是布尔表达式永远不要放数字或可能为0的变量。炸弹2三元嵌套超过两层// ❌ 三个月后你自己都看不懂return(div{isLoading ?Spinner /:error ?ErrorMessageerror{error}/:data ? data.items.length0?Listitems{data.items}/:EmptyState /:null}/div);这段代码现在能跑。但下周产品说加载失败要有重试按钮你要在哪层加哪层是error的分支看3分钟才能确认。// ✅ 早返回 分离functionDataView({isLoading, error, data}){if(isLoading)returnSpinner /;if(error)returnErrorMessageerror{error}/;if(!data||data.items.length0)returnEmptyState /;returnListitems{data.items}/;}原则三元表达式最多嵌套一层。超过一层就拆成if早返回或抽成子组件。炸弹3条件渲染导致组件状态丢失// ❌ 切换tab后表单输入全丢了functionTabPanel({activeTab}){return(div{activeTabformUserForm /}{activeTabpreviewPreview /}/div);}用户在表单里填了一堆数据切到 Preview 看看效果切回来——表单空了。因为条件为false时组件被卸载了所有内部 state 都丢了。// ✅ 方案1用 CSS 隐藏而不是卸载functionTabPanel({activeTab}){return(divdivstyle{{display: activeTabform?block:none}}UserForm //divdivstyle{{display: activeTabpreview?block:none}}Preview //div/div);}// ✅ 方案2状态提升到父组件functionTabPanel({activeTab}){const[formData, setFormData]useState({});return(div{activeTabform(UserFormdata{formData}onChange{setFormData}/)}{activeTabpreviewPreviewdata{formData}/}/div);}原则条件渲染 卸载/重新挂载。如果组件有用户输入或内部状态要么用 CSS 隐藏要么把状态提升。炸弹4用 useEffect state 做派生条件// ❌ 用useEffect同步计算一个本该直接派生的值functionProductList({products, category}){const[filteredProducts, setFilteredProducts]useState([]);useEffect((){ setFilteredProducts(products.filter(pp.categorycategory));},[products, category]);return(div{filteredProducts.length0?Listitems{filteredProducts}/:EmptyState /}/div);}看着好像没问题。但这里有两个bug首次渲染闪烁组件先渲染一次空列表useState([])然后useEffect执行后触发第二次渲染。用户会看到 EmptyState 闪一下。不必要的渲染每次 products/category 变化会触发两次渲染——一次正常渲染、一次 useEffect 触发的 setState。// ✅ 直接计算不要同步functionProductList({products, category}){const filteredProductsproducts.filter(pp.categorycategory);return(div{filteredProducts.length0?Listitems{filteredProducts}/:EmptyState /}/div);}没有 state没有 effect没有闪烁没有多余渲染。如果 filter 逻辑很重用useMemo——但别用useEffectsetState。原则能从 props/state 直接算出来的值就直接算。不要用 useEffect 同步到另一个 state。这个也是我之前写过的 useEffect 反模式之一——但在条件渲染场景中特别容易犯。炸弹5key 相同导致条件切换时状态串台// ❌ 两个输入框共享了同一个DOM状态互相污染functionAuthForm({mode}){return(div{modelogin?inputplaceholder用户名/:inputplaceholder邮箱/}/div);}用户在登录模式输入了用户名切到注册模式——邮箱输入框里出现了刚才输入的用户名。原因React 的 reconciliation 认为这是同一个input节点相同位置、相同类型所以复用 DOM不重置 state。// ✅ 给不同分支加不同的 key强制React重建functionAuthForm({mode}){return(div{modelogin?inputkeyloginplaceholder用户名/:inputkeyregisterplaceholder邮箱/}/div);}key 不同 → React 销毁旧节点、创建新节点 → state 不会串台。// 复杂场景整个表单组件切换{modelogin?LoginFormkeylogin/:RegisterFormkeyregister/}原则当条件渲染的两个分支是不同的东西但DOM结构相似时必须加不同的key防止状态串台。速查表定时炸弹触发条件修复 渲染 “0”左侧是数字/NaN改用 0或!!三元嵌套超过2层拆成 if 早返回状态丢失条件false卸载CSS隐藏或状态提升派生条件用effectuseEffectsetState直接计算/useMemokey串台同位置同类型DOM加不同key你踩过哪颗这5个里面我在生产环境踩过3颗。第1颗渲染0和第5颗key串台是线上bug排查了半小时才定位到的——因为本地测试数据永远不为0永远不切换模式。条件渲染看着简单但它直接关系到React的渲染机制、reconciliation算法、组件生命周期。写错了不会报错只会在某个边界条件下默默炸给用户看。你踩过哪颗炸弹评论区说说。
返回列表