引言
在多市场业务场景下,企业常按区域拆分独立开发团队,但不同市场既存在通用业务逻辑,又有差异化需求。若缺乏统一的代码规范和协作模式,极易出现代码复制粘贴、差异化逻辑维护困难、团队合并成本高等问题。本文结合 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 的开发模式存在以下问题:
- 代码分散:单个特性的属性 / 方法分散在 data、computed、methods 等选项中,迭代后单文件体积过大;
- 复杂度升高:业务迭代导致单文件代码行数剧增,维护成本显著上升;
- 逻辑与模板耦合:业务逻辑与模板强绑定,差异化逻辑需直接在模板中硬编码。
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的工具函数返回值
},
};
};
问题核心分析:
- 上下文耦合 :函数功能完全依赖外部传入的
self实例,无法独立复用和测试; - 类型不安全 :
self无明确类型约束,解构的属性是否存在、类型是否匹配均无法在编译期校验; - 维护成本高:筛选配置与组件内部属性强绑定,组件重构或属性更名时,需同步修改该函数,且属性来源分散,定位问题难度大;
- 复用性差:该函数只能在特定组件场景下调用,无法在非组件文件(如通用工具库)中复用。
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 层添加对应配置;
- 维护成本低:视图和逻辑分离,修改样式不影响业务逻辑,修改业务逻辑不影响视图渲染。
总结
- Hooks 层通过响应式配置 + 地区配置覆盖,解决了传统 Schema 响应性缺失、if-else 硬编码的问题,实现细粒度逻辑复用;
- Schema 层组合多个 Hooks 生成场景级配置,屏蔽底层细节,同时封装场景级数据 / 事件逻辑;
- Scene 层作为统一入口,结合构建时注入的市场标识实现按需分发,支持 Tree-shaking 优化;
- View 层仅负责渲染,与业务逻辑完全解耦,大幅降低多市场场景的维护成本。
4 方案验证与价值分析
4.1 核心问题解决效果
- 横向复用问题:分层设计 + 公共分支协作,实现"通用逻辑复用、差异逻辑独立开发",彻底解决复制粘贴导致的合并难题;
- 多场景定制化问题 :通过
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 生效前提
为确保构建工具能精准剔除无关代码,需满足以下条件:
__REGION__必须是编译期常量,而非运行时变量(如从接口获取、从 localStorage 读取);- 场景分发逻辑(Scene 层)需使用静态条件判断 (if / else),避免动态函数调用(如
import(${bizType}.ts)); - 项目需启用 ES 模块(ESM)规范(
import/export),CommonJS 模块(require)无法被 Tree-shaking; - 避免将差异化逻辑封装为"副作用函数"(如修改全局变量、直接操作 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 协作流程
- 技术方案评审:跨市场团队共同评审,明确通用逻辑与差异化点;
- 公共分支搭建:建立 feature 公共分支,共同开发通用 Hooks / Schema;
- 分支开发:从公共分支创建 dev 分支,独立开发各市场差异化逻辑;
- 代码合并:按需求时间线将 dev 分支合并到 feature 分支,避免差异过大。
5.2 协作原则
- 通用逻辑:公共分支协作开发,确保复用性和一致性;
- 差异逻辑:各自 dev 分支独立开发,避免相互干扰;
- 定期同步:每周同步分支进度,及时解决合并冲突。
6 总结与落地建议
6.1 方案核心特点
- 分层清晰:View / Scene / Schema / Hooks 四层职责明确,解耦业务逻辑与视图;
- 组合优先:通过 Hooks 组合替代继承 / Mixins,避免耦合问题;
- 配置驱动:差异化逻辑通过配置覆盖实现,无硬编码 if-else;
- 构建优化:Tree-shaking 减少资源体积,提升加载性能;
- 易迁移性:为未来技术栈迁移(如 React)奠定基础。
6.2 实施建议
- 逐步迁移:按业务模块逐步重构,避免一次性全量改造;
- 规范先行:制定 Hooks 命名(如 useXXX)、粒度(单一职责)、文档规范;
- 工具支撑:开发 Hooks 模板 / 验证工具,降低使用门槛;
- 持续优化:定期评审 Hooks 设计,沉淀通用工具库。
6.3 后续优化方向
- 完善 TypeScript 类型定义,提升类型安全性;
- 开发 Hooks 生成 / 校验工具,降低使用门槛;
- 建立性能监控体系,优化高频场景性能;
- 搭建 Hooks 文档中心,方便团队查阅 / 复用。