告别React混乱三元表达式:我用守卫组件的重构之旅

499 阅读3分钟

bf5d1e0c-d668-4019-8288-8b03362a7cd3.png

凌晨3点,我盯着自己写的代码陷入沉思——那些嵌套的三元表达式像藤蔓一样缠绕着整个组件,连我自己都看不懂了...

那是一个支付页面的代码片段:

{isPaid ? (
  <section className="flex-1 flex flex-col xl:flex-row max-w-7xl mx-auto w-full p-4 sm:p-6 gap-6 xl:gap-8">
    <div className="flex-1 flex flex-col gap-6">
      <h2 className="text-lg font-semibold">Pagamento já realizado</h2>
      <p className="text-sm text-gray-500">Obrigado por sua compra!</p>
      <Button variant="outline" onClick={handleClick}>
        Ver comprovante
      </Button>
    </div>
  </section>
) : (
  <>
    {(isPixModalOpen && invoice?.pix) && <PixModal ... />}
    <section className="flex-1 flex flex-col xl:flex-row max-w-7xl mx-auto w-full p-4 sm:p-6 gap-6 xl:gap-8">
      <div className="flex-1 flex flex-col gap-6">
        <PaymentMethodSelector />
        <PaymentDetails />
        <SecurityInfo />
      </div>
      <OrderSummary />
    </section>
  </>
)

当时的我:虽然有点丑,但能跑就行,对吧?直到在同一个文件里第四次看到这样的代码:

{isPaid ? (
  <div>巨型JSX内容A</div>
) : ( 
  <div>巨型JSX内容B</div>
)

那一刻我意识到——必须重构!


我的顿悟时刻

看着满屏的?:,我想起了Inertia.js的<WhenVisible>组件。为什么不创造自己的守卫组件?

第一版:通用守卫

// 15行代码的救赎
function When({ condition, children }) {
  return condition ? <>{children}</> : null;
}

// 使用示例
<When condition={isPaid}>
  <PaymentSuccessView />
</When>

<When condition={!isPaid}>
  <PaymentForm />
</When>

效果:代码缩进减少40%,可读性提升200%!

第二版:语义化组件

当支付逻辑扩展到5个页面时,我升级了方案:

// 支付专用守卫
function WhenPaid({ children }) {
  const { paymentStatus } = usePayment();
  return paymentStatus === 'PAID' ? <>{children}</> : null;
}

// 未支付守卫
function WhenNotPaid({ children }) {
  const { paymentStatus } = usePayment();
  return paymentStatus === 'PENDING' ? <>{children}</> : null;
}

// 优雅的使用方式
<WhenPaid>
  <ReceiptView />
</WhenPaid>

<WhenNotPaid>
  <PaymentOptions />
</WhenNotPaid>

守卫组件的威力:我的五大收获

  1. 代码即文档
    看到<WhenAdmin>就知道这是管理员专属区域

  2. 告别括号地狱
    再也不用数))}}的匹配关系

  3. 逻辑与UI分离
    支付状态判断被封装在组件内部

  4. 测试变得简单

    // 测试用例直击核心
    test('WhenPaid shows children only when paid', () => {
      render(<WhenPaid><div data-testid="content"/></WhenPaid>);
      expect(screen.queryByTestId('content')).toBeNull();
      
      mockPaymentStatus('PAID');
      expect(screen.getByTestId('content')).toBeInTheDocument();
    });
    
  5. 跨组件复用
    同一个<WhenFeatureFlag>用在12个不同页面


我的进阶守卫模式

1. 智能包裹组件

解决"有时需要包裹div,有时不需要"的难题:

function ConditionalWrapper({ condition, wrapper, children }) {
  return condition ? wrapper(children) : children;
}

// 使用示例
<ConditionalWrapper 
  condition={needsPadding}
  wrapper={children => <div className="p-4">{children}</div>}
>
  <UserProfile />
</ConditionalWrapper>

2. 权限守卫系统

// 权限守卫组件
function WhenRole({ role, children }) {
  const { user } = useAuth();
  return user.role === role ? <>{children}</> : null;
}

// 真实项目使用
<WhenRole role="ADMIN">
  <DashboardAdminPanel />
</WhenRole>

<WhenRole role="EDITOR">
  <ContentEditor />
</WhenRole>

3. 功能开关守卫

// 功能开关组件
function WhenFeature({ feature, children }) {
  const { isEnabled } = useFeatureFlags();
  return isEnabled(feature) ? <>{children}</> : null;
}

// 灰度发布控制
<WhenFeature feature="NEW_CHECKOUT_UI">
  <CheckoutV2 />
</WhenFeature>

<WhenFeature feature="OLD_CHECKOUT_UI">
  <CheckoutV1 />
</WhenFeature>

血泪教训:何时不用守卫组件

经过6个月实战,我总结出这些反模式

适合三元表达式的情况:

// 简单条件 - 一目了然
{isLoading ? <Spinner /> : <Content />}

// 短路渲染 - 干净利落
{hasItems && <ItemList />}

// 文本微调 - 没必要组件化
<span>{error || 'No issues found'}</span>

守卫组件翻车现场:

// 过度抽象 - 更难懂了!
<When condition={!!(user && user.isAdmin && features.editMode)}>
  <EditButton />
</When>

// 还不如直接写:
{user?.isAdmin && features.editMode && <EditButton />}

我的守卫组件实施法则

  1. 渐进式采用
    从最痛的if-else开始重构,不要强求一步到位

  2. 命名即契约
    组件名必须清晰表达业务逻辑:
    <WhenSubscriptionActive>
    <ConditionalWrapper3>

  3. 团队共识优先
    在代码评审时演示对比案例:

    | 指标          | 三元表达式 | 守卫组件 | 改进幅度 |
    |--------------|------------|----------|----------|
    | 可读性        | ★★☆        | ★★★★★    | +150%    |
    | 可维护性      | ★★☆        | ★★★★☆    | +100%    |
    | 逻辑复用      | 不可能     | 轻松复用  | ∞        |
    
  4. 性能警戒线
    复杂条件使用useMemo

    function WhenComplex({ children }) {
      const shouldRender = useMemo(() => {
        // 复杂计算逻辑
      }, [deps]);
      
      return shouldRender ? children : null;
    }
    

重构前后的心灵感悟

"以前我觉得代码能跑就行,现在明白代码是写给人看的——守卫组件就像给代码加上了路标,让后续维护者(包括三个月后的我自己)不再迷失在条件判断的迷宫中。"

最后送你的礼物:在我的个人工具箱里珍藏的守卫组件模板:

import React from 'react';

interface GuardProps {
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

// 基础守卫模板
export const When = ({
  condition,
  children,
  fallback = null
}: GuardProps & { condition: boolean }) => {
  return condition ? <>{children}</> : <>{fallback}</>;
};

// 权限守卫
export const WhenRole = ({
  role,
  children,
  fallback
}: GuardProps & { role: string }) => {
  const { user } = useUser();
  return (
    <When condition={user.role === role} fallback={fallback}>
      {children}
    </When>
  );
};

// 功能开关守卫
export const WhenFeature = ({
  feature,
  children,
  fallback
}: GuardProps & { feature: string }) => {
  const { isActive } = useFeatureToggle();
  return (
    <When condition={isActive(feature)} fallback={fallback}>
      {children}
    </When>
  );
};

真理时刻:当我在代码评审中展示重构对比时,团队Leader说:"这就像把迷宫地图换成了GPS导航"。守卫组件不改变功能,但彻底改变了开发体验——这大概就是优雅代码的力量。