
本文探讨如何科学组织 React 项目中的可复用组件,重点解决“按功能拆分(如 RefuseButton/ValidateButton)”与“过度泛化(如单一 ButtonComponent)”之间的平衡问题,提出基于职责分离、逻辑抽离和目录约定的工程化实践方案。
本文探讨如何科学组织 react 项目中的可复用组件,重点解决“按功能拆分(如 `refusebutton`/`validatebutton`)”与“过度泛化(如单一 `buttoncomponent`)”之间的平衡问题,提出基于职责分离、逻辑抽离和目录约定的工程化实践方案。
在实际 React 开发中,追求“高复用性”不等于追求“最少组件数”,而应聚焦于可理解、可测试、可演进的组件设计。你当前将 Refuse 和 Validate 拆分为独立组件是合理的起点,但问题在于它们同时承担了三类职责:UI 渲染(按钮样式)、状态控制(useState、useDispatch)、业务规则判断(何时禁用、何时弹窗)。这种耦合导致组件难以复用、难以测试,也阻碍了团队协作。
✅ 推荐结构:三层职责分离法
我们建议采用「UI 组件 + 业务逻辑层 + 容器组合」的三层架构,替代“一个文件包打天下”或“纯 props 驱动的万能按钮”:
| 层级 | 职责 | 示例 |
|---|---|---|
| UI 组件(Presentational) | 纯视觉呈现,无业务逻辑、无副作用;接收所有必要 props,不访问 Redux 或 hooks |
<primarybutton></primarybutton>, <iconbutton></iconbutton>, <loadingbutton></loadingbutton>
|
| 业务逻辑(Domain Logic) | 独立函数或 Hook,封装领域规则(如 canRefuse(offer)、shouldShowValidationPopup(offer)),与 UI 解耦 |
src/logic/offerPermissions.ts |
| 容器组件(Container / Composition) | 在具体页面或布局中,组合 UI 组件与业务逻辑,处理状态连接(Redux、form hooks 等) |
Header.tsx 中的 <refusebutton></refusebutton>(此时它只是一个轻量包装) |
▪ 示例重构:从 Validate 到三层结构
-
UI 层(
src/components/ui/Button.tsx)import { Button, ButtonProps } from '@mui/material';
interface BaseButtonProps extends ButtonProps { loading?: boolean; children: React.ReactNode; }
export const PrimaryButton = ({ loading, children, ...props }: BaseButtonProps) => (
); ```
-
业务逻辑层(
src/logic/offerRules.ts)// 纯函数,无副作用,可单元测试 export const canValidateOffer = (offer: Offer, userRole: string[]) => { if (!userRole?.length) return false; if (offer.Status === 1 || offer.Status === 3 || offer.Status === 4) return false;
// 角色权限规则 if (userRole.includes('1') && userRole.includes('3')) return offer.Status === 0 || offer.Status === 2; if (userRole.includes('2')) return offer.Status === 1; if (userRole.includes('3')) return offer.Status === 2; return false; };
export const needsAdminValidation = (offer: Offer) => ['Classique', 'Visa'].includes(offer.Paiement) && offer.Status === 2;
3. **容器层(`src/features/header/RefuseButton.tsx`)**
```tsx
import { PrimaryButton } from '@/components/ui/Button';
import { canRefuseOffer } from '@/logic/offerRules';
import { useDispatch, useSelector } from 'react-redux';
import { setOpen, setActiveButton } from '@/features/popup.slice';
import { FormDialog } from '@/components/popup/FormDialog';
interface RefuseButtonProps {
offerData: Offer;
register: UseFormRegister<offer>;
watch: UseFormWatch<offer>;
setValue: UseFormSetValue<offer>;
errors: FieldErrors<offer>;
}
export const RefuseButton = ({
offerData,
register,
watch,
setValue,
errors,
}: RefuseButtonProps) => {
const dispatch = useDispatch();
const isLoading = useSelector((state: RootState) => state.orders.loading);
const userRole = useSelector((state: RootState) => state.user?.userGroup);
// 业务规则驱动渲染
if (!canRefuseOffer(offerData, userRole)) return null;
return (
<primarybutton disabled onclick="{()"> {
dispatch(setOpen(true));
dispatch(setActiveButton('Refuser'));
}}
>
{isLoading ? 'Chargement' : 'Refuser'}
</primarybutton><formdialog offerdata="{offerData}" register="{register}" watch="{watch}" setvalue="{setValue}" errors="{errors}"></formdialog>
>
);
};</offer></offer></offer></offer>
? 目录结构建议(Scaleable & Searchable)
src/ ├── components/ │ ├── ui/ # 纯 UI 组件(Button, Card, Input...),无业务依赖 │ └── popup/ # 通用弹窗(AdminValidation, FormDialog) ├── logic/ # 领域逻辑函数(offerRules.ts, authRules.ts) ├── features/ # 功能模块(orders/, header/),含容器组件和切片 │ ├── header/ │ │ ├── Header.tsx # 页面级组件 │ │ └── RefuseButton.tsx # 容器组件(轻量!仅组合) │ └── orders/ ├── features/ # Redux Toolkit slices(buttons.slice.ts, popup.slice.ts)
⚠️ 关键注意事项
-
避免“万能组件陷阱”:
<buttoncomponent action="%7B%7D" texte="{}"></buttoncomponent>表面简洁,实则将所有业务判断上推至调用方,导致Header.tsx变得臃肿、重复且难以维护。 -
早期返回优于嵌套条件:在容器组件中优先使用
if (!condition) return null;,提升可读性。 -
逻辑必须可测试:
canRefuseOffer()应能脱离 React 环境独立运行,便于编写 Jest 单元测试。 -
语言与加载状态交由 UI 层处理:
texte={isLoading ? 'Chargement' : 'Valider'}这类逻辑应下沉到PrimaryButton内部,通过loadingprop 统一控制。 -
后端增强是终极优化:如答案所建议,理想情况下
offer对象本身应包含actions: { canRefuse: true, canValidate: false }字段,前端仅做渲染,大幅降低客户端复杂度。
通过这种组织方式,你的 Header 将变得极其简洁:
export const Header = () => (
<refusebutton offerdata="{offerData}" ...form props></refusebutton><validatebutton offerdata="{offerData}" ...form props></validatebutton>
>
);
组件数量未必减少,但每个组件的职责清晰、边界明确、易于替换和测试——这才是规模化 React 项目可持续演进的核心。











