React条件渲染

发布于:2025-07-19 ⋅ 阅读:(16) ⋅ 点赞:(0)

下面,我们来系统的梳理关于 条件渲染 的基本知识点:


一、基础概念

1.1 什么是条件渲染?

条件渲染是指在 React 中根据特定条件决定渲染不同 UI 内容的技术。它是构建动态用户界面的核心能力,允许开发者根据应用状态、用户权限、数据加载情况等因素展示不同的界面元素。

1.2 为什么需要条件渲染?

  • 动态UI:根据应用状态变化更新界面
  • 权限控制:展示不同用户角色对应的内容
  • 状态管理:处理加载中、错误、空数据等状态
  • 响应式设计:根据不同设备尺寸调整布局
  • 用户体验优化:逐步展示内容提升感知性能

1.3 条件渲染的核心原则

true
false
条件表达式
布尔值
渲染组件A
渲染组件B

二、条件渲染的 7 种实现方式

2.1 if/else 语句

function UserGreeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>欢迎回来!</h1>;
  } else {
    return <h1>请先登录。</h1>;
  }
}

2.2 三元运算符

function NotificationIcon({ count }) {
  return (
    <div className="notification">
      {count > 0 
        ? <span className="badge">{count}</span>
        : null
      }
      <BellIcon />
    </div>
  );
}

2.3 逻辑与(&&)运算符

function AdminPanel({ user }) {
  return (
    <div>
      <h1>管理面板</h1>
      {user.isAdmin && (
        <div className="admin-tools">
          <UserManagement />
          <SystemSettings />
        </div>
      )}
    </div>
  );
}

2.4 立即执行函数(IIFE)

function ComplexRenderer({ status, data }) {
  return (
    <div className="content">
      {(() => {
        switch (status) {
          case 'loading':
            return <Spinner size="large" />;
          case 'error':
            return <ErrorDisplay message="加载失败" />;
          case 'empty':
            return <EmptyState />;
          case 'success':
            return <DataGrid data={data} />;
          default:
            return null;
        }
      })()}
    </div>
  );
}

2.5 变量存储JSX

function ThemeSelector({ theme }) {
  let content;
  
  if (theme === 'dark') {
    content = (
      <div className="dark-theme">
        <MoonIcon />
        <span>深色模式</span>
      </div>
    );
  } else {
    content = (
      <div className="light-theme">
        <SunIcon />
        <span>浅色模式</span>
      </div>
    );
  }
  
  return <div className="theme-selector">{content}</div>;
}

2.6 组件封装

function ConditionalRender({ condition, fallback, children }) {
  return condition ? children : fallback || null;
}

// 使用
<ConditionalRender 
  condition={isDataLoaded}
  fallback={<Loader />}
>
  <DataTable data={data} />
</ConditionalRender>

2.7 高阶组件(HOC)

function withPermission(requiredRole, Component) {
  return function ({ user, ...props }) {
    if (user.role !== requiredRole) {
      return <NoPermission />;
    }
    return <Component {...props} />;
  };
}

// 使用
const AdminDashboard = withPermission('admin', Dashboard);

三、条件渲染的最佳实践

3.1 避免过早返回问题

// 不推荐:可能遗漏公共元素
function Page({ isLoading }) {
  if (isLoading) return <Loader />;
  
  return (
    <div>
      <Header />
      <MainContent />
      <Footer />
    </div>
  );
}

// 推荐:保持结构一致
function Page({ isLoading }) {
  return (
    <div>
      <Header />
      {isLoading ? <Loader /> : <MainContent />}
      <Footer />
    </div>
  );
}

3.2 空状态处理

function ProductList({ products }) {
  if (products.length === 0) {
    return (
      <div className="empty-state">
        <EmptyBoxIcon />
        <p>暂无产品数据</p>
        <button>添加新产品</button>
      </div>
    );
  }
  
  return (
    <div className="product-grid">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

3.3 复杂条件处理

function AuthForm({ mode }) {
  const isLogin = mode === 'login';
  const isRegister = mode === 'register';
  const isReset = mode === 'reset';
  
  return (
    <form>
      {!isReset && (
        <InputField 
          label="邮箱" 
          name="email" 
          type="email" 
        />
      )}
      
      {!isReset && isRegister && (
        <InputField 
          label="用户名" 
          name="username" 
        />
      )}
      
      <InputField 
        label={isReset ? "新密码" : "密码"} 
        name="password" 
        type="password"
      />
      
      {isReset && (
        <InputField 
          label="确认密码" 
          name="confirmPassword" 
          type="password" 
        />
      )}
    </form>
  );
}

四、条件渲染性能优化

4.1 避免不必要的重新渲染

// 使用 React.memo 优化
const ExpensiveComponent = React.memo(function({ data }) {
  // 复杂渲染逻辑
});

function Parent({ showComponent }) {
  return (
    <div>
      {showComponent && <ExpensiveComponent data={largeDataSet} />}
    </div>
  );
}

4.2 条件渲染与组件生命周期

function ToggleComponent({ show }) {
  // 当 show 为 false 时,组件会被卸载
  return show ? <ExpensiveComponent /> : null;
}

function KeepAliveComponent({ show }) {
  // 使用 CSS 控制显示隐藏,保持组件状态
  return (
    <div style={{ display: show ? 'block' : 'none' }}>
      <ExpensiveComponent />
    </div>
  );
}

4.3 条件渲染中的 key 属性

function AuthForm({ mode }) {
  // 使用 key 强制重置组件状态
  return (
    <div>
      {mode === 'login' ? (
        <LoginForm key="login" />
      ) : (
        <RegisterForm key="register" />
      )}
    </div>
  );
}

五、条件渲染在状态管理中的应用

5.1 加载状态处理

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const result = await api.fetchData();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, []);

  if (loading) return <FullPageLoader />;
  if (error) return <ErrorDisplay error={error} />;
  return <DataView data={data} />;
}

5.2 权限控制

function ProtectedRoute({ user, requiredRole, children }) {
  if (!user) {
    return <Navigate to="/login" />;
  }
  
  if (!user.roles.includes(requiredRole)) {
    return (
      <div className="permission-denied">
        <h2>权限不足</h2>
        <p>您没有访问此页面的权限</p>
      </div>
    );
  }
  
  return children;
}

// 使用
<Routes>
  <Route path="/admin" element={
    <ProtectedRoute requiredRole="admin">
      <AdminDashboard />
    </ProtectedRoute>
  } />
</Routes>

5.3 空数据处理

function UserDashboard({ user }) {
  return (
    <div className="dashboard">
      <h1>欢迎, {user.name}</h1>
      
      <ConditionalRender
        condition={user.projects.length > 0}
        fallback={
          <EmptyState
            icon={<ProjectIcon />}
            title="暂无项目"
            description="您还没有创建任何项目"
            action={<Button>创建项目</Button>}
          />
        }
      >
        <ProjectList projects={user.projects} />
      </ConditionalRender>
      
      <ConditionalRender
        condition={user.tasks.length > 0}
        fallback={
          <EmptyState
            icon={<TaskIcon />}
            title="暂无任务"
            description="您当前没有待处理任务"
          />
        }
      >
        <TaskList tasks={user.tasks} />
      </ConditionalRender>
    </div>
  );
}

六、条件渲染的测试策略

6.1 测试工具

  • Jest:JavaScript 测试框架
  • React Testing Library:React 组件测试工具
  • @testing-library/user-event:模拟用户交互

6.2 测试用例示例

test('显示加载状态', () => {
  render(<DataFetcher isLoading={true} />);
  expect(screen.getByTestId('loader')).toBeInTheDocument();
});

test('显示错误信息', () => {
  render(<DataFetcher error="Network Error" />);
  expect(screen.getByText('网络错误')).toBeInTheDocument();
});

test('显示数据内容', () => {
  const mockData = [{ id: 1, name: '测试数据' }];
  render(<DataFetcher data={mockData} />);
  expect(screen.getByText('测试数据')).toBeInTheDocument();
});

test('空状态显示正确', () => {
  render(<ProductList products={[]} />);
  expect(screen.getByText('暂无产品数据')).toBeInTheDocument();
  expect(screen.getByText('添加新产品')).toBeInTheDocument();
});

七、条件渲染常见问题与解决方案

7.1 条件渲染导致状态丢失

问题:组件在条件渲染切换时状态被重置

{showFormA ? <FormA /> : <FormB />}

解决方案

// 方案1:使用CSS控制显示隐藏
<div style={{ display: showFormA ? 'block' : 'none' }}>
  <FormA />
</div>
<div style={{ display: showFormA ? 'none' : 'block' }}>
  <FormB />
</div>

// 方案2:添加key属性保留状态
{showFormA 
  ? <FormA key="formA" /> 
  : <FormB key="formB" />}

7.2 复杂条件逻辑维护

问题:多重嵌套条件导致代码难以阅读和维护

{isLoading ? (
  <Loader />
) : error ? (
  <Error />
) : data ? (
  data.length > 0 ? (
    <List />
  ) : (
    <Empty />
  )
) : null}

解决方案

// 方案1:状态机模式
const stateMachine = {
  loading: <Loader />,
  error: <Error error={error} />,
  empty: <Empty />,
  success: <List data={data} />
};

return stateMachine[currentState];

// 方案2:自定义Hook
function useContentState({ isLoading, error, data }) {
  if (isLoading) return 'loading';
  if (error) return 'error';
  if (!data || data.length === 0) return 'empty';
  return 'success';
}

// 组件中使用
const contentState = useContentState({ isLoading, error, data });
return stateComponents[contentState];

7.3 条件渲染中的副作用

问题:条件渲染导致useEffect执行异常

{condition && <Component />}

解决方案:确保副作用依赖项正确设置

useEffect(() => {
  // 依赖项变化时执行
}, [dependency]);

八、条件渲染设计模式

8.1 渲染属性(Render Props)

function AuthProvider({ children }) {
  const [user] = useAuth();
  
  return children({
    isAuthenticated: !!user,
    isAdmin: user?.role === 'admin'
  });
}

// 使用
<AuthProvider>
  {({ isAuthenticated, isAdmin }) => (
    <div>
      {isAuthenticated ? <Dashboard /> : <Login />}
      {isAdmin && <AdminPanel />}
    </div>
  )}
</AuthProvider>

8.2 状态驱动UI

function FileUploader() {
  const [state, setState] = useState('idle'); // idle, uploading, success, error
  
  const renderState = {
    idle: (
      <div>
        <UploadButton onClick={() => setState('uploading')} />
        <DropZone onDrop={handleDrop} />
      </div>
    ),
    uploading: (
      <ProgressBar 
        value={progress} 
        onCancel={() => setState('idle')}
      />
    ),
    success: <SuccessMessage onReset={() => setState('idle')} />,
    error: <ErrorMessage onRetry={() => setState('uploading')} />
  };
  
  return <div className="uploader">{renderState[state]}</div>;
}

8.3 组件插槽(Slots)

function Card({ header, footer, children }) {
  return (
    <div className="card">
      {header && <div className="card-header">{header}</div>}
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// 使用
<Card
  header={<h3>用户资料</h3>}
  footer={
    <div className="actions">
      <Button>保存</Button>
      <Button variant="secondary">取消</Button>
    </div>
  }
>
  <UserForm />
</Card>

九、实战案例:响应式导航栏

function ResponsiveNavbar() {
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const isMobile = useMediaQuery('(max-width: 768px)');

  return (
    <nav className="navbar">
      <div className="logo">我的应用</div>
      
      {isMobile ? (
        <button 
          className="menu-toggle"
          onClick={() => setIsMenuOpen(!isMenuOpen)}
        >
          <MenuIcon />
        </button>
      ) : (
        <div className="desktop-nav">
          <NavLink to="/">首页</NavLink>
          <NavLink to="/about">关于</NavLink>
          <NavLink to="/contact">联系我们</NavLink>
        </div>
      )}
      
      {/* 移动端菜单 */}
      {isMobile && isMenuOpen && (
        <div className="mobile-menu">
          <NavLink to="/" onClick={() => setIsMenuOpen(false)}>首页</NavLink>
          <NavLink to="/about" onClick={() => setIsMenuOpen(false)}>关于</NavLink>
          <NavLink to="/contact" onClick={() => setIsMenuOpen(false)}>联系我们</NavLink>
        </div>
      )}
    </nav>
  );
}

// 媒体查询Hook
function useMediaQuery(query) {
  const [matches, setMatches] = useState(false);
  
  useEffect(() => {
    const media = window.matchMedia(query);
    if (media.matches !== matches) {
      setMatches(media.matches);
    }
    
    const listener = () => setMatches(media.matches);
    media.addListener(listener);
    
    return () => media.removeListener(listener);
  }, [query, matches]);
  
  return matches;
}

十、总结与最佳实践

10.1 条件渲染技术选择指南

场景 推荐技术
简单二元条件 三元运算符或逻辑与(&&)
多分支条件 switch 语句或对象字面量
复杂条件逻辑 提取为函数或自定义 Hook
组件树差异大 if/else 提前返回
保留组件状态 CSS 显示隐藏或 key 属性

10.2 最佳实践原则

  1. 保持可读性:避免过度嵌套的三元表达式
  2. 组件化思维:将条件逻辑封装为独立组件
  3. 关注性能:使用 React.memo 优化渲染
  4. 全面状态处理:覆盖所有可能的状态(加载、错误、空数据等)
  5. 测试覆盖:确保所有条件分支都有测试用例
  6. 渐进式展示:复杂界面分阶段渲染提升用户体验

10.3 常见错误避免

  • 在条件渲染中直接修改状态(应使用状态更新函数)
  • 忘记处理可能的空值或未定义状态
  • 在条件分支中返回多个元素而没有包裹容器
  • 忽略条件渲染对组件生命周期的影响
  • 过度使用内联函数导致性能问题

网站公告

今日签到

点亮在社区的每一天
去签到