解决多市场业务复用与差异化痛点:Vue Composition API 分层架构方案

引言

在多市场业务场景下,企业常按区域拆分独立开发团队,但不同市场既存在通用业务逻辑,又有差异化需求。若缺乏统一的代码规范和协作模式,极易出现代码复制粘贴、差异化逻辑维护困难、团队合并成本高等问题。本文结合 Vue Composition API 设计分层架构方案,针对性解决多市场业务的复用与差异化开发痛点,并配套协作规范,降低长期维护成本。

1 业务背景与核心问题

1.1 组织架构与需求分类

1.1.1 组织架构背景

业务按不同市场拆分独立团队承接需求,各团队负责对应市场的业务开发,未来存在团队合并的可能性。本文方案聚焦此类场景,平衡"独立开发"与"逻辑复用"的需求。

1.1.2 需求分类(核心适配场景)

本方案重点解决B 类、D 类(存在复用与差异)需求的开发问题,四类需求定义如下:

类型 描述 典型示例
A 类 中大需求,某市场独有业务模块 / 逻辑 特定市场专属的营销模块
B 类 中大需求,大部分逻辑差异,小部分复用 不同市场的业务转移功能
C 类 中大需求,大部分逻辑复用,小部分差异 多市场通用的评分系统
D 类 小需求,不同市场逻辑差异 字段展示、校验规则差异化

注:B 类需求首个版本通常规模较大,后续迭代多以 D 类需求形式开展。

1.2 核心问题优先级

优先级 问题类型 问题描述
重要且紧急 横向复用问题 无技术约束导致团队复制粘贴开发,后续代码合并难度剧增、维护成本翻倍(与组织架构拆分直接相关)
重要但不紧急 多场景定制化问题 大量使用 if-else 处理差异化逻辑,代码可读性、可维护性差(多市场业务通用痛点)

2 现状分析:多市场开发的核心痛点

2.1 技术栈(Vue Options API)的局限性

当前基于 Vue Options API 的开发模式存在以下问题:

  1. 代码分散:单个特性的属性 / 方法分散在 data、computed、methods 等选项中,迭代后单文件体积过大;
  2. 复杂度升高:业务迭代导致单文件代码行数剧增,维护成本显著上升;
  3. 逻辑与模板耦合:业务逻辑与模板强绑定,差异化逻辑需直接在模板中硬编码。

2.2 现有复用方案的问题

为减少单文件代码量,项目中常用三种复用方式,但均存在明显缺陷:

复用方式 实现方式 核心问题
组件化 抽离独立业务逻辑为组件 无明显问题,是基础复用手段
Mixins 混入 抽离逻辑为 Mixin,通过 mixins 选项混入组件 1. 属性来源不清晰;2. 命名空间冲突;3. 多 Mixin 隐式耦合;4. 仅支持 SFC(Single-File Component,Vue 单文件组件) 场景
Schema 独立 抽离 Schema 为 JS 变量 / 函数,传 this / bind 上下文使用 1. 静态 Schema 响应性不足;2. 需手动维护上下文;3. 属性来源分散

2.3 典型问题案例

2.3 典型问题案例

案例 1:静态 Schema 响应性缺失

传统方式定义表格列配置时,直接将响应式变量的取值结果赋值给 hide 属性,导致该属性成为常量,无法随响应式变量变化而更新,只能依赖接口请求时序保证渲染正确性,存在潜在的渲染异常风险。

typescript 复制代码
// 定义类型(提升代码可维护性与类型安全)
interface BizColumnConfig {
  label: string;
  key: string;
  width: number;
  render: (row: Record<string, any>, val: any) => string;
  hide: boolean;
}

// 问题代码:静态赋值导致响应性缺失
const customBizColumn: BizColumnConfig = {
  label: $gt('Custom Business Column'),
  key: 'custom_biz_column',
  width: 110,
  render(row, val) {
    // 优化点:增加空值判断,避免数组方法调用时报错
    if (!row?.function_type_list) return '-';
    
    if (row.function_type_list.includes(functionTypes.value['BusinessType'])) {
      return bizColumnMapping[val] || '-';
    }
    return '-';
  },
  // 核心问题:bizColumnToggle.value 即时取值后赋值给 hide,成为常量
  // 当 bizColumnToggle.value 变化时,hide 不会同步更新
  hide: !bizColumnToggle.value,
};

问题核心分析

  • hide 属性赋值时直接取用 bizColumnToggle.value 的当前值,而非绑定响应式引用;
  • 即使后续 bizColumnToggle.value 发生变化,customBizColumn.hide 仍保持初始值,导致列的显示/隐藏状态无法动态更新;
  • 只能通过"确保接口请求在渲染前完成"的方式规避问题,增加了业务逻辑的耦合性和维护成本。
案例 2:原生 JS 函数依赖上下文

通过传入 self(Vue 组件实例)获取组件内部属性和方法来构建筛选配置,存在上下文强耦合、属性来源不清晰、类型不安全等问题,且 self 的属性范围无明确约束,维护时需频繁回溯组件代码,大幅提升维护成本。

typescript 复制代码
interface BizFilterItem {
  label: string;
  key: string;
  type: string;
}

interface BizFilterConfig {
  [key: string]: BizFilterItem | Function;
}

// 问题代码:依赖 self 上下文,耦合性高且维护成本高
export const BizFilterConfigMap = (
  self: Record<string, any>, // 无明确类型约束的组件实例
): BizFilterConfig => {
  // 核心问题:从 self 中解构属性,来源分散且无类型提示
  // 一旦组件内部方法名变更,此处无法提前感知,易引发运行时错误
  const { getBizRegionFilter } = self;

  // 内部函数仍依赖 self 上下文,耦合性进一步传递
  const getBizStationNameFilter = ({ filterName, filterKey, multiple = false }): BizFilterItem => {
    // ... 通用实现逻辑(需从 self 中获取更多上下文属性)
    return {
      label: filterName || $gt('Business Station Name'),
      key: filterKey,
      type: multiple ? 'multiple-select' : 'select',
    };
  };

  // 函数调用仍需传递 self,上下文依赖无法解耦
  const bizTaskStatusOptions = getBizTaskStatusOptions(self);

  return {
    biz_order_id: {
      label: $gt('Business Order ID'),
      key: 'biz_order_id',
      type: 'input',
    },
    biz_region_id: getBizRegionFilter,
    biz_station_id: {
      label: $gt('Business Station ID'),
      key: 'biz_station_id',
      type: 'input',
    },
    biz_station_name: getBizStationNameFilter({
      filterKey: 'biz_station_name',
    }),
    biz_station_ids: getBizStationNameFilter({
      filterKey: 'biz_station_ids',
      multiple: true,
    }),
    biz_task_status: {
      label: $gt('Business Task Status'),
      key: 'biz_task_status',
      type: 'select',
      options: bizTaskStatusOptions, // 依赖self的工具函数返回值
    },
  };
};

问题核心分析

  1. 上下文耦合 :函数功能完全依赖外部传入的 self 实例,无法独立复用和测试;
  2. 类型不安全self 无明确类型约束,解构的属性是否存在、类型是否匹配均无法在编译期校验;
  3. 维护成本高:筛选配置与组件内部属性强绑定,组件重构或属性更名时,需同步修改该函数,且属性来源分散,定位问题难度大;
  4. 复用性差:该函数只能在特定组件场景下调用,无法在非组件文件(如通用工具库)中复用。

3 解决方案:基于 Composition API 的分层架构

3.1 核心技术选型:为什么选 Composition API?

引入 Vue Composition API 重构代码分层,核心优势如下:

  • 逻辑聚合:按功能维度收拢代码,替代 Options API 的分散式组织;
  • 无耦合复用:细粒度 Hooks 替代 Mixins,避免命名冲突和隐式耦合;
  • 类型友好:天然支持 TypeScript,类型推断更完善;
  • 上下文清晰:无需依赖 this 绑定,避免上下文丢失 / 错乱问题。

典型的 Composition API 代码结构

typescript 复制代码
setup() {
  // 1. 声明响应式引用(替代 Options API 的 data)
  const bizDialogFormRef = ref(null); // 修复:Vue Composition API 中 ref 无需数组解构
  const bizTableRef = ref(null);
  
  // 2. 声明通用业务方法(按功能聚合,替代 Options API 的 methods 分散定义)
  const handleTableRefresh = () => {
    bizTableRef.value?.onSearch(); // 增加可选链,避免空值报错
  };

  // 3. 按功能拆分的 Hooks(核心:逻辑聚合 + 细粒度复用)
  // 表格列配置 Hook:收拢所有表格列相关逻辑
  const {
    bizRequestId,
    bizTrackingNo,
    bizStatus,
    bizOperator,
    bizActions
  } = useBizTableColumns(handleTableRefresh); 

  // 枚举选项 Hook:收拢所有业务枚举相关逻辑
  const {
    bizMidStatusOptions,
    bizInterceptReasonOptions
  } = useBizExceptionEnum();

  // 弹窗逻辑 Hook:收拢所有上传弹窗相关逻辑
  const {
    bizDialogRules,
    bizDialogForm,
    bizDialogVisible,
    setBizDialogVisible,
    handleBizFileUpload,
    handleBizDialogConfirm
  } = useBizUploadDialog(handleTableRefresh);

  // 表格数据 Hook:收拢所有数据加载相关逻辑
  const {
    bizRequestLoading,
    bizRequestData,
    loadBizTableData
  } = useBizTableData();

  // 4. 暴露给模板的变量/方法(按需暴露,清晰可控)
  return {
    bizDialogFormRef,
    bizTableRef,
    handleTableRefresh,
    bizRequestId,
    bizDialogRules,
    bizRequestLoading,
    loadBizTableData
  };
}

代码说明

  • 对比 Options API 分散在 data/methods/computed 的写法,Composition API 按"表格列、枚举、弹窗、数据加载"等功能维度聚合逻辑,可读性和维护性大幅提升;
  • Hooks 支持参数传递(如 handleTableRefresh),实现逻辑间的显式依赖,替代 Mixins 的隐式耦合;
  • 无需依赖 this,所有变量/方法的来源清晰可追溯。

3.2 四层架构设计(核心)

针对多市场差异化场景,将需求实现分为四层,职责边界清晰,解耦业务逻辑与视图:
View 层:视图渲染
Scene 层:场景分发
Schema 层:配置组合
Hooks 层:细粒度逻辑
Biz Station Layer(可选):站点差异化
基础工具/API:底层支撑

层级 核心职责 设计目标
View 层 页面组件渲染,调用 Scene 层获取配置 / 逻辑 与地区 / 场景解耦,仅负责渲染
Scene 层 根据地区 / 场景标识(REGION / bizType)分发 Schema 统一入口,支持构建时 Tree-shaking 掉与本地区无关的代码
Schema 层 组合多个 Hooks,生成完整场景配置(表格 / 表单 / 弹窗等) 聚合配置,屏蔽底层 Hooks 细节
Hooks 层 按字段 / 功能抽离最小粒度逻辑(支持响应式 + 差异化配置) 逻辑复用,支持地区配置覆盖
Biz Station Layer(可选) 处理不同站点类型的显著差异 适配站点级特殊逻辑

3.3 目录结构规范

复制代码
页面目录/
├── list.vue              # View 层:页面视图(仅渲染,无业务逻辑)
├── hooks/
│   ├── useBizTransferSchema/  # Scene 层:场景分发入口
│   │   ├── index.ts             # 核心:根据地区/场景分发对应 Schema
│   │   ├── useSpecificBizTransferTask.ts  # Schema 层:专属业务转移配置
│   │   └── useGeneralBizTransferTask.ts # Schema 层:通用业务转移配置
│   └── useBusinessType.ts  # Hooks 层:业务类型细粒度逻辑
└── components/
    └── biz-export-dropdown.vue # 通用组件(复用基础组件)

目录说明

  • 按"层级"划分目录,Hooks 层按"功能 / 字段"拆分文件,Schema 层按"业务场景"拆分文件,Scene 层作为统一入口;
  • 所有业务逻辑收敛到 hooks 目录,View 层仅保留渲染逻辑,职责边界清晰。

3.4 各层级实现详解

3.4.1 Hooks 层:细粒度逻辑复用

按字段 / 功能维度抽离 Hooks,收拢单字段 / 单功能的所有相关逻辑(配置、枚举、响应式计算),支持响应式和地区差异化配置覆盖(核心替代 if-else 硬编码):

typescript 复制代码
// 补充类型定义,提升类型安全
interface BusinessTypeRegionConfig {
  label?: string;
  hide?: boolean | (() => boolean);
  render?: (row: Record<string, any>) => string;
}

interface BusinessTypeTableColumn {
  label: string;
  prop: string;
  render: (row: Record<string, any>) => string;
  hide: boolean | (() => boolean);
}

export function useBusinessType() {
  // 1. 响应式数据(关联全局状态,替代 Options API 的 computed)
  const businessTypeEnum = computed(store.state.enums.systemEnums.business_type_enum || {});
  const businessTypeFlag = computed(store.state.businessConfig.businessFunctionFlag.business_type_flag);
  const driverDecision = computed(store.state.businessConfig.driverDecision);
  
  // 响应式计算:业务类型是否显示(自动响应依赖变化)
  const hasBusinessType = computed(() => businessTypeFlag === true && driverDecision === false);
  
  // 2. 通用枚举处理(复用基础 Hook)
  const { options: businessTypeOptions, mapping: businessTypeMapping } = useBizMappingOptions(businessTypeEnum);
  
  // 3. 地区差异化配置(核心:替代运行时 if-else 硬编码)
  // 按市场标识配置差异化规则,构建时可 Tree-shaking 无关配置
  const regionConfig: Record<string, BusinessTypeRegionConfig> = {
    RegionA: {
      label: $gt('Business Type For Region A'), // 多语言适配
      hide: false,
    },
    RegionB: { 
      label: $gt('Business Type For Region B'),
      render: (row) => `${businessTypeMapping[row.business_type]} - RegionB`, // 差异化渲染
    }
  };
  
  // 4. 响应式表格列配置(支持地区配置覆盖)
  const businessTypeTableColumn = computed<BusinessTypeTableColumn>(() => {
    return {
      label: $gt('Business Type'), // 默认多语言标签
      prop: 'business_type',
      render: (row) => businessTypeMapping[row.business_type] || '-', // 默认渲染逻辑
      hide: () => !hasBusinessType.value, // 默认隐藏规则
      ...regionConfig[__REGION__], // 地区配置覆盖(优先级更高)
    };
  });
  
  // 5. 响应式表单项配置(复用同一套地区配置)
  const businessTypeFormItem = computed(() => {
    return {
      label: $gt('Business Type'),
      key: 'business_type',
      type: 'radio-group',
      selectOptions: businessTypeOptions,
      ...regionConfig[__REGION__], // 复用地区配置,避免重复定义
    };
  });
  
  // 6. 暴露对外的变量/方法(按需暴露,清晰可控)
  return {
    hasBusinessType,
    businessTypeMapping,
    businessTypeTableColumn, // 响应式列配置(自动更新)
    businessTypeFormItem,   // 响应式表单项配置
    businessTypeOptions
  };
}

代码说明

  • 响应式:所有配置通过 computed 声明,依赖的 businessTypeFlag / driverDecision 变化时,配置会自动更新(解决传统 Schema 响应性缺失问题);
  • 差异化:通过 regionConfig 配置覆盖替代 if-else,代码更简洁、可维护性更高,新增市场仅需添加配置项;
  • 复用性:单 Hook 同时支撑表格列、表单项的配置,避免重复编写逻辑;
  • 无耦合:Hook 不依赖任何组件上下文,可在任意文件中调用,复用范围覆盖 SFC / JS / TS 文件。
3.4.2 Schema 层:配置组合

组合多个字段级 Hooks,生成特定业务场景的完整配置(聚合细粒度逻辑,屏蔽底层细节),同时包含数据获取、事件处理等场景级逻辑:

typescript 复制代码
// 补充类型定义
interface BizTableConfig {
  startCollapseRowNum: number;
  table: {
    loading: boolean;
    width: number;
    bizActionsWidth: number;
    columns: any[];
    actions: Array<{ label: string; click: (row: any) => void; hide: boolean }>;
  };
}

export function useSpecificBizTransferTask() {
  // 1. 获取通用工具/路由实例(按需获取,替代 this 上下文)
  const { proxy } = getCurrentInstance() || {};
  const router = proxy?.$router;
  
  // 2. 组合多个细粒度 Hooks(核心:复用字段级逻辑,无耦合)
  const { bizTaskId } = useBizTaskId(); // 任务ID字段逻辑
  const { transferInitiator } = useTransferInitiator(); // 转移发起方字段逻辑
  const { transferRecipient } = useTransferRecipient(); // 转移接收方字段逻辑
  const { bizStationColumn } = useBizStationColumn(); // 站点字段逻辑
  const { bizTransferStatus } = useBizTransferStatus(); // 转移状态字段逻辑
  
  // 权限 Hook(复用通用权限逻辑)
  const { hasBizViewPermission } = useBizPermission();
  
  // 3. 数据加载逻辑(复用通用 Loading 封装 Hook)
  const { 
    error: bizTableError, 
    loading: bizTableLoading, 
    loadingWrappedFunction: specificBizTransferFetcher 
  } = useBizLoadingWrapper(async (params) => {
    return await api.getBizTransferList(params); // 业务接口请求
  });
  
  // 4. 响应式表格数据(修复:ref 无需数组解构)
  const bizTableData = ref([]);
  
  // 5. 场景级数据获取方法(封装通用逻辑)
  const bizTableFetcher = async (params: Record<string, any>) => {
    if (!specificBizTransferFetcher) return;
    
    try {
      const { data = {} } = await specificBizTransferFetcher(params);
      bizTableData.value = data.task_list || []; // 响应式更新数据
    } catch (err) {
      bizTableData.value = [];
      console.error('获取转移任务列表失败:', err); // 增加错误捕获
    }
  };
  
  // 6. 场景级事件处理
  const handleBizExport = (command: string) => {
    console.info('导出指令:', command);
    // 导出逻辑(可复用通用导出 Hook)
  };
  
  // 7. 响应式表格配置(聚合所有字段配置 + 场景配置)
  const bizTableConfig = computed<BizTableConfig>(() => {
    return {
      startCollapseRowNum: 2,
      table: {
        loading: bizTableLoading, // 响应式加载状态
        width: 1100,
        bizActionsWidth: 80,
        // 聚合多个字段 Hook 的列配置
        columns: [
          bizTaskId,
          transferInitiator,
          transferRecipient,
          bizStationColumn,
          bizTransferStatus
        ],
        // 场景级操作按钮配置
        actions: [
          {
            label: $gt('View'),
            click: (row) => {
              router?.push({ 
                name: 'bizTransferDetail', 
                params: { bizTransferTaskId: row.biz_transfer_task_id } 
              });
            },
            hide: !hasBizViewPermission, // 响应式权限控制
          },
        ],
      },
    };
  });
  
  // 8. 暴露场景级配置/方法(屏蔽底层 Hook 细节)
  return {
    bizTableData,
    bizTableConfig, // 完整表格配置
    bizTableFetcher, // 数据获取方法
    bizCustomActions: { handleBizExport }, // 自定义事件
  };
}

代码说明

  • 组合复用:通过组合多个细粒度 Hooks,快速构建完整场景配置,避免重复编写字段级逻辑;
  • 上下文解耦:通过 getCurrentInstance 按需获取实例,替代传统模式下 self 上下文的强耦合;
  • 错误处理:增加接口请求的错误捕获,提升代码健壮性;
  • 屏蔽细节:对外仅暴露场景级的配置 / 方法(如 bizTableConfig / bizTableFetcher),底层 Hook 变化不影响上层调用。
3.4.3 Scene 层:场景分发(支持 Tree-shaking)

根据构建时注入的 __REGION__(市场标识,由 Webpack / Vite 等构建工具注入)分发对应 Schema,构建阶段可剔除与当前市场无关的代码,减少包体积:

typescript 复制代码
// 补充类型定义
type BizType = 'specific' | 'special' | 'general';

/**
 * 业务转移场景 Schema 分发入口
 * @param bizType 业务类型(specific/special/general)
 * @returns 对应场景的完整配置
 */
export default function useBizTransferSchema(bizType: BizType) {
  // 核心:根据市场标识分发 Schema,构建时 Tree-shaking 无关代码
  if (__REGION__ === 'RegionA') {
    if (bizType === 'specific') return useSpecificBizTransferTask();
    if (bizType === 'special') return useSpecialBizTransferTask();
  } 
  // 其他市场默认返回通用配置
  return useGeneralBizTransferTask();
}

代码说明

  • 统一入口:所有业务场景的配置都通过该函数获取,View 层无需关注底层差异化逻辑;
  • Tree-shaking 优化:构建时 __REGION__ 会被替换为具体市场标识(如 RegionA),无关分支代码会被剔除(如 useGeneralBizTransferTask 不会被打包进 RegionA 的产物);
  • 扩展性强:新增市场 / 业务类型仅需添加分支判断,不影响现有逻辑。
3.4.4 View 层:视图解耦渲染

View 层仅负责组件渲染,通过调用 Scene 层获取配置和逻辑,完全与具体业务逻辑、地区差异化解耦:

typescript 复制代码
<template>
  <div class="biz-transfer-page">
    <!-- 通用表格组件:仅接收配置和数据,无业务逻辑 -->
    <biz-core
      ref="bizTableRef"
      :config="bizTableConfig"
      :data="bizTableData"
      @search="handleBizTableSearch"
    />
    <!-- 通用导出组件:按需渲染 -->
    <biz-export-dropdown 
      v-if="bizCustomActions.handleBizExport" 
      @command="bizCustomActions.handleBizExport" 
    />
  </div>
</template>

<script lang="ts">
// 补充 TS 标识,提升类型支持
import { defineComponent, ref } from '@vue/composition-api';
import { BIZ_DEFAULT_PAGE_SIZE } from '~/utils/const';
import BizExportDropdown from './components/biz-export-dropdown.vue';
import useBizTransferSchema from './hooks/useBizTransferSchema/index';

export default defineComponent({
  name: 'BizTransferList', // 补充组件名,便于调试
  components: { BizExportDropdown },
  setup(_, context) {
    // 1. 获取路由参数(仅做参数传递,无业务逻辑)
    const route = context.root.$route;
    const bizType = route.params.bizType as BizType; // 类型断言,提升类型安全
    
    // 2. 调用 Scene 层(核心:无需关注底层差异化逻辑)
    const { 
      bizTableData, 
      bizTableConfig, 
      bizTableFetcher, 
      bizCustomActions 
    } = useBizTransferSchema(bizType);
    
    // 3. 通用查询逻辑(仅做参数组装,无业务逻辑)
    const bizTableRef = ref(null);
    const handleBizTableSearch = async () => {
      // 组装查询参数(从表格组件获取表单数据)
      const params = { 
        page: 1, 
        pageSize: BIZ_DEFAULT_PAGE_SIZE, 
        ...bizTableRef.value?.formData 
      };
      await bizTableFetcher(params); // 调用 Scene 层的方法
    };
    
    // 4. 暴露给模板的变量/方法(仅渲染相关)
    return { 
      bizTableRef, 
      bizTableConfig, 
      bizTableData, 
      handleBizTableSearch, 
      bizCustomActions 
    };
  },
});
</script>

代码说明

  • 完全解耦:View 层仅负责渲染,不包含任何业务逻辑、差异化判断,所有复杂逻辑都收敛到 Scene / Hooks 层;
  • 通用性强:新增市场 / 业务类型时,View 层无需修改,仅需在 Scene / Hooks 层添加对应配置;
  • 维护成本低:视图和逻辑分离,修改样式不影响业务逻辑,修改业务逻辑不影响视图渲染。

总结

  1. Hooks 层通过响应式配置 + 地区配置覆盖,解决了传统 Schema 响应性缺失、if-else 硬编码的问题,实现细粒度逻辑复用;
  2. Schema 层组合多个 Hooks 生成场景级配置,屏蔽底层细节,同时封装场景级数据 / 事件逻辑;
  3. Scene 层作为统一入口,结合构建时注入的市场标识实现按需分发,支持 Tree-shaking 优化;
  4. View 层仅负责渲染,与业务逻辑完全解耦,大幅降低多市场场景的维护成本。

4 方案验证与价值分析

4.1 核心问题解决效果

  1. 横向复用问题:分层设计 + 公共分支协作,实现"通用逻辑复用、差异逻辑独立开发",彻底解决复制粘贴导致的合并难题;
  2. 多场景定制化问题 :通过regionConfig配置覆盖替代运行时 if-else,代码可维护性大幅提升。

4.2 组合优于继承的设计逻辑

继承存在"脆弱基类问题"(基类修改影响所有子类)和"大猩猩-香蕉问题"(继承得到不需要的逻辑),而组合模式更适配前端多变的业务场景:

  • 组合定义"对象做什么"(按需组合 Hooks),继承定义"对象是什么";
  • 组合模式下,逻辑拆分 / 重构成本更低,可灵活适配多市场差异化需求。

基类
继承子类 1
继承子类 2
组合模块 1
组合对象
组合模块 2
组合模块 3

4.3 Composition API vs Options API 对比

Options API 问题 Composition API 优势
逻辑分散,代码组织不灵活 逻辑聚合,按功能维度组织
Mixins 复用存在耦合 / 冲突 细粒度 Hooks 无耦合,复用更灵活
TypeScript 类型推断差 天然支持 TS,类型更安全
依赖 this 上下文,易出错 无 this 绑定,上下文清晰

4.4 方案优缺点

维度 具体内容
优点 1. 代码直觉化:组合式 Hooks 符合开发直觉,分层抹平差异; 2. 迭代便捷:新增功能仅需添加 Hooks 并组合到 Schema 层; 3. 资产沉淀:可积累通用 Hooks,提升团队效率; 4. 构建优化:Tree-shaking 减少静态资源体积; 5. 易迁移:向 React Hooks 迁移成本低
缺点 1. 短期成本:团队需学习 Composition API(如响应式 .value 处理); 2. 组件改造:部分组件需适配 Schema 传入方式; 3. 颗粒度控制:需合理设计 Hooks 粒度,避免过粗 / 过细; 4. 性能开销:Vue 2.6 + Composition API 存在轻微性能开销(极端场景才明显)

4.5 Tree-shaking 优化收益:按需打包多市场代码

在多市场业务场景下,不同市场的差异化逻辑会导致代码包包含大量"当前市场无需执行"的冗余代码,通过构建时注入 __REGION__ 环境标识并结合 Tree-shaking 机制,可精准剔除无关市场的代码,显著降低包体积、提升页面加载性能。

4.5.1 实现原理

__REGION__ 是由构建工具(Webpack/Vite)在编译阶段注入的常量标识 (如 RegionA/RegionB),而非运行时变量。构建工具会基于该常量的取值,静态分析代码分支并剔除"永远不会执行"的代码路径,最终仅保留当前市场所需逻辑。

构建工具配置示例(以 Vite 为例):

bash 复制代码
# package.json 中配置不同市场的构建命令
{
  "scripts": {
    "build:regionA": "vite build --define __REGION__='\"RegionA\"'",
    "build:regionB": "vite build --define __REGION__='\"RegionB\"'"
  }
}

Webpack 配置示例(通过 DefinePlugin 注入):

javascript 复制代码
// webpack.config.js
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      __REGION__: JSON.stringify(process.env.REGION || 'General')
    })
  ]
};

构建前(包含所有市场代码):

typescript 复制代码
import useSpecificBizTransferTask from './useSpecificBizTransferTask';
import useSpecialBizTransferTask from './useSpecialBizTransferTask';
import useGeneralBizTransferTask from './useGeneralBizTransferTask';

export default (bizType) => {
  if (__REGION__ === '指定市场') {
    if (bizType === 'specific') {
      return useSpecificBizTransferTask();
    }
    if (bizType === 'special') {
      return useSpecialBizTransferTask();
    }
  } else {
    return useGeneralBizTransferTask();
  }
}

构建后(仅保留指定市场代码):

typescript 复制代码
import useSpecificBizTransferTask from './useSpecificBizTransferTask';
import useSpecialBizTransferTask from './useSpecialBizTransferTask';

export default (bizType) => {
  if (bizType === 'specific') {
    return useSpecificBizTransferTask();
  }
  if (bizType === 'special') {
    return useSpecialBizTransferTask();
  }
}
4.5.2 收益量化示例

以"业务转移功能"模块为例,该模块包含 RegionA 专属逻辑(30KB)、RegionB 专属逻辑(25KB)、通用逻辑(45KB),不同构建方式的包体积对比如下:

构建方式 包体积 冗余代码占比 核心优化点
未做 Tree-shaking(包含所有市场) 100KB 55%(60KB 冗余) 无,所有市场逻辑均打包
构建时注入 REGION(RegionA) 75KB 0% 仅保留 RegionA 专属逻辑 + 通用逻辑
构建时注入 REGION(RegionB) 70KB 0% 仅保留 RegionB 专属逻辑 + 通用逻辑

原始代码:100KB

(通用45KB + RegionA 30KB + RegionB 25KB)
构建注入 REGION=RegionA
构建注入 REGION=RegionB
最终包:75KB

(通用45KB + RegionA 30KB)
最终包:70KB

(通用45KB + RegionB 25KB)
包体积减少25%,加载速度提升~20%
包体积减少30%,加载速度提升~25%

注:实际收益与"差异化代码占比"强相关------差异化逻辑越多的模块(如 B 类、D 类需求),Tree-shaking 收益越显著,核心业务模块包体积可降低 20%-40%,首屏加载时间缩短 15%-30%。

4.5.3 Tree-shaking 生效前提

为确保构建工具能精准剔除无关代码,需满足以下条件:

  1. __REGION__ 必须是编译期常量,而非运行时变量(如从接口获取、从 localStorage 读取);
  2. 场景分发逻辑(Scene 层)需使用静态条件判断 (if / else),避免动态函数调用(如 import(${bizType}.ts));
  3. 项目需启用 ES 模块(ESM)规范(import/export),CommonJS 模块(require)无法被 Tree-shaking;
  4. 避免将差异化逻辑封装为"副作用函数"(如修改全局变量、直接操作 DOM),构建工具会因无法判断副作用而保留代码。
4.5.4 业务价值
  • 性能层面:包体积减小直接降低网络传输耗时和解析耗时,尤其适配低网速的海外市场场景;
  • 维护层面:无需为不同市场维护独立的代码分支,通过"一份代码 + 构建注入标识"实现按需打包,降低分支管理成本;
  • 安全层面:无关市场的业务逻辑不会被打包到产物中,减少敏感逻辑暴露的风险。

4.6 性能与迁移性优化

性能优化建议
  • 对大对象使用shallowRef / shallowReactive,减少响应式追踪开销;
  • 静态数据使用Object.freeze,优化内存占用;
  • 高频渲染的 Hooks 逻辑做缓存(如computed / watch防抖)。
未来迁移 React 的兼容性

Composition API 与 React Hooks 均基于函数式编程思想,迁移成本低:

  • 仅需转换 API 调用方式(如 Vue 的ref→React 的useState);
  • 细粒度 Hooks 拆分使迁移仅需调整函数内部实现,输入输出保持一致。

5 多团队协作方案

5.1 协作流程

  1. 技术方案评审:跨市场团队共同评审,明确通用逻辑与差异化点;
  2. 公共分支搭建:建立 feature 公共分支,共同开发通用 Hooks / Schema;
  3. 分支开发:从公共分支创建 dev 分支,独立开发各市场差异化逻辑;
  4. 代码合并:按需求时间线将 dev 分支合并到 feature 分支,避免差异过大。

5.2 协作原则

  1. 通用逻辑:公共分支协作开发,确保复用性和一致性;
  2. 差异逻辑:各自 dev 分支独立开发,避免相互干扰;
  3. 定期同步:每周同步分支进度,及时解决合并冲突。

6 总结与落地建议

6.1 方案核心特点

  1. 分层清晰:View / Scene / Schema / Hooks 四层职责明确,解耦业务逻辑与视图;
  2. 组合优先:通过 Hooks 组合替代继承 / Mixins,避免耦合问题;
  3. 配置驱动:差异化逻辑通过配置覆盖实现,无硬编码 if-else;
  4. 构建优化:Tree-shaking 减少资源体积,提升加载性能;
  5. 易迁移性:为未来技术栈迁移(如 React)奠定基础。

6.2 实施建议

  1. 逐步迁移:按业务模块逐步重构,避免一次性全量改造;
  2. 规范先行:制定 Hooks 命名(如 useXXX)、粒度(单一职责)、文档规范;
  3. 工具支撑:开发 Hooks 模板 / 验证工具,降低使用门槛;
  4. 持续优化:定期评审 Hooks 设计,沉淀通用工具库。

6.3 后续优化方向

  1. 完善 TypeScript 类型定义,提升类型安全性;
  2. 开发 Hooks 生成 / 校验工具,降低使用门槛;
  3. 建立性能监控体系,优化高频场景性能;
  4. 搭建 Hooks 文档中心,方便团队查阅 / 复用。
相关推荐
深念Y2 小时前
一个Bug:Vue Router 4.3.0 导致浏览器窗口无法最小化
前端·vue.js·bug·窗口·最小化·bilibili·视频网站
湛海不过深蓝2 小时前
【procomponents】根据表单查询表格数据的两种写法
前端·javascript·react.js
大Mod_abfun2 小时前
AntdUI教程#1ChatList交互(vb.net)
服务器·前端·ui·交互·antdui·聊天框
憧憬成为web高手2 小时前
xss学习记录--xss-lab部署
前端·学习·xss
窝子面2 小时前
十四、弹窗组件
前端
局i2 小时前
从零封装第一个 Vue 组件:极简入门指南
前端·javascript·vue.js
Jave21082 小时前
Vue下一个大版本会是怎样?它的最终目标是怎样的?
前端·vue.js·经验分享