You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React项目可复用组件的最优组织方式咨询

问题

我正在开发一个React项目,没有导师指导,不确定当前组件组织方向是否正确。我希望尽可能复用组件,目前组件按类别/视图组织,比如buttons目录下有Refuse、Validate等近十个功能按钮。

当前代码结构示例:

Header组件中调用按钮

import { Refuse } from 'components/buttons/Refuse';
import { Validate } from 'components/buttons/Validate';

export const Header = () => {
  return (
    <>
      <Refuse
        offerData={offerData}
        setValue={setValue}
        onSubmit={onSubmit}
        register={register}
        watch={watch}
      />
      <Validate
        register={register}
        setError={setError}
        offerData={offerData}
        setValue={setValue}
        watch={watch}
        clearErrors={clearErrors}
      />
    </>
  );
};

Validate按钮组件实现(简化版)

import { Button } from '@mui/material';
import { useDispatch, useSelector } from 'react-redux';

export const Validate = ({ offerData, setError, register }) => {
  const dispatch = useDispatch();
  const isLoading = useSelector(state => state?.orders.loading);
  const userRole = useSelector(state => state.user?.userGroup);

  return (
    <>
      {/* 大量权限、状态判断逻辑 */}
      {userRole?.includes('1') && userRole?.includes('3') ? (
        <Button
          disabled={[1,3,4].includes(offerData?.Status)}
          onClick={() => {/* 业务逻辑 */}}
        >
          {isLoading ? 'Chargement' : 'Valider'}
        </Button>
      ) : (
        <Button
          disabled={/* 另一套权限判断 */}
          onClick={() => {/* 业务逻辑 */}}
        >
          {isLoading ? 'Chargement' : 'Valider'}
        </Button>
      )}
      {/* 关联的弹窗组件 */}
    </>
  );
};

我考虑删除这些单功能按钮文件,创建一个通用的ButtonComponent:

import { Button } from '@mui/material';

export const ButtonComponent = ({ type, action, texte, ...props }) => {
  return (
    <Button type={type} onClick={action} {...props}>
      {texte}
    </Button>
  );
};

但这样会导致调用页面的代码量剧增,大量业务逻辑(权限判断、状态禁用、弹窗触发)都要写到Header里。想咨询:组织React可复用组件的最优方式是什么?


回答

1. 先区分「基础UI组件」和「业务组件」

复用的核心不是减少文件数量,而是减少重复逻辑,所以要把组件分成两类:

  • 基础UI组件:只负责通用的UI样式和基础交互,不绑定业务逻辑。比如你想做的ButtonComponent,应该封装MUI Button的通用逻辑(比如加载状态、统一样式、禁用状态的基础处理),供所有业务组件调用。
  • 业务组件:封装特定业务场景的逻辑,比如RefuseButton、ValidateButton,把权限判断、状态校验、弹窗触发、Redux操作这些和业务强相关的逻辑都封装在里面,调用方只需要传必要的业务数据(比如offerData)即可。

2. 优化组件目录结构

可以按「通用UI」和「业务模块」拆分目录,避免混乱:

src/
├─ components/
│  ├─ ui/               # 基础通用组件
│  │  ├─ ButtonComponent.jsx
│  │  ├─ InputComponent.jsx
│  │  └─ DialogComponent.jsx
│  └─ business/         # 业务绑定组件
│     ├─ orders/        # 订单模块相关组件
│     │  ├─ RefuseButton.jsx
│     │  ├─ ValidateButton.jsx
│     │  └─ OrderHeader.jsx
│     └─ users/         # 用户模块相关组件
└─ utils/               # 通用工具函数
   └─ buttonPermissions.js

3. 重构你的按钮组件方案

第一步:封装基础UI Button

先把通用逻辑(比如加载状态、统一禁用处理)放到ButtonComponent里:

import { Button } from '@mui/material';

export const ButtonComponent = ({
  isLoading,
  loadingText = 'Chargement',
  children,
  ...props
}) => {
  // 统一处理加载时的禁用状态
  const finalDisabled = props.disabled || isLoading;

  return (
    <Button disabled={finalDisabled} {...props}>
      {isLoading ? loadingText : children}
    </Button>
  );
};

第二步:重构业务按钮,抽离重复逻辑

把权限判断、状态校验这些重复逻辑抽到工具函数里,比如utils/buttonPermissions.js:

// 判断Refuse按钮是否应该禁用
export const shouldDisableRefuseButton = (userRole, offerData, isLoading) => {
  if (isLoading) return true;
  
  if (userRole?.includes('1') && userRole?.includes('3')) {
    return ![0, 2].includes(offerData?.Status);
  }
  if (userRole?.includes('1') && !userRole?.includes('2')) {
    return offerData?.Status !== 0;
  }
  if (userRole?.includes('2') && !userRole?.includes('1')) {
    return offerData?.Status !== 1;
  }
  if (userRole?.includes('3')) {
    return offerData?.Status !== 2;
  }
  return true;
};

// 判断Validate按钮是否应该禁用
export const shouldDisableValidateButton = (userRole, offerData, isLoading) => {
  if (isLoading) return true;
  
  if (userRole?.includes('1') && userRole?.includes('3')) {
    return [1,3,4].includes(offerData?.Status);
  }
  if (userRole?.includes('1')) {
    return offerData?.Status !== 0;
  }
  if (userRole?.includes('2')) {
    return offerData?.Status !== 1;
  }
  if (userRole?.includes('3')) {
    return offerData?.Status !== 2;
  }
  return true;
};

然后重构RefuseButton业务组件:

import { useDispatch, useSelector } from 'react-redux';
import { setActiveButton } from '../../features/buttons.slice';
import { setOpen } from '../../features/popup.slice';
import { ButtonComponent } from '../../ui/ButtonComponent';
import { FormDialog } from '../popup';
import { shouldDisableRefuseButton } from '../../utils/buttonPermissions';

export const RefuseButton = ({ register, watch, setValue, offerData, errors }) => {
  const dispatch = useDispatch();
  const isLoading = useSelector(state => state?.orders.loading);
  const userRole = useSelector(state => state.user?.userGroup);
  
  // 调用工具函数判断禁用状态
  const isDisabled = shouldDisableRefuseButton(userRole, offerData, isLoading);

  const handleClick = () => {
    dispatch(setOpen(true));
    dispatch(setActiveButton('Refuser'));
  };

  return (
    <>
      <ButtonComponent
        variant="text"
        type="button"
        onClick={handleClick}
        isLoading={isLoading}
        disabled={isDisabled}
      >
        Refuser
      </ButtonComponent>
      <FormDialog
        offerData={offerData}
        register={register}
        watch={watch}
        setValue={setValue}
        errors={errors}
      />
    </>
  );
};

第三步:调用业务组件更简洁

Header里调用时,只需要传必要的业务数据,逻辑都封装在组件内部:

import { RefuseButton } from 'components/business/orders/RefuseButton';
import { ValidateButton } from 'components/business/orders/ValidateButton';

export const Header = () => {
  return (
    <>
      <RefuseButton
        offerData={offerData}
        setValue={setValue}
        register={register}
        watch={watch}
        errors={errors}
      />
      <ValidateButton
        offerData={offerData}
        setError={setError}
        register={register}
        setValue={setValue}
        watch={watch}
        clearErrors={clearErrors}
      />
    </>
  );
};

4. 核心原则总结

  • 不要把业务逻辑扩散到页面:如果多个页面需要用到同一个按钮,把逻辑封装在业务组件里,改逻辑时只需要改一处。
  • 基础组件做通用,业务组件做专属:基础组件负责统一UI和通用交互,业务组件绑定特定场景的逻辑,两者配合才能最大化复用。
  • 抽离重复逻辑到工具函数:组件里的判断逻辑、计算逻辑尽量抽到工具函数,让组件代码更简洁,也方便复用和测试。

内容的提问来源于stack exchange,提问作者Elodie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 13:55:55