下面是我们交出的答案——不追求表面复用,只追求改了不炸。分享我们团队在重构移动端文件上传组件时的一些思考,不谈太多高大上的概念,而是聊聊如何用适度的设计解决实际问题。
原来的实现存在这些问题:
- 逻辑散落 - 上传逻辑分散在各个业务页面,复制粘贴到处都是
- 扩展困难 - 新增一种上传方式(比如视频)需要改多个文件
- 维护困难 - 改一处逻辑要同时改多个页面
- 样式不统一 - 每个页面都有自己的上传按钮样式
于是我们决定抽取公共能力,但关键问题是:怎么抽?抽到什么程度?
我们定了三个小目标:
- 统一入口 - 所有文件上传都走同一套组件
- 可配置 - 不同业务场景可以灵活定制
- 可扩展 - 新增能力时不破坏现有代码
先上一张架构图:
┌─────────────────────────────────────────────────────┐
│ 业务页面 │ │ (表单页面、详情页面、数据填报页面…) │ └─────────────────────┬───────────────────────────────┘
│ ▼
┌─────────────────────────────────────────────────────┐ │ useFileUploadActions (Hook) │ │ 组合业务逻辑,返回 action 数组 │ └─────────────────────┬───────────────────────────────┘
│ ┌───────────┼───────────┐ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ │ 拍照用例 │ │ 上传用例 │ │ 视频用例 │ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ ▼ ▼ ▼
┌─────────────────────────────────────┐ │ Adapter 层 (适配器) │ │ 适配底层 native API (相机/文件) │ └─────────────────────────────────────┘
│ ▼
┌─────────────────────────────────────┐ │ 原生能力 (Native API) │ └─────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐ │ UI 组件层 │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 简单上传组件 │ │ 标签上传组件 │ │ │ │ (Plain模式) │ │ (Labeling模式)│ │ │ └──────────────┘ └──────────────┘ │ │ │ │ │ │ └────────┬───────┘ │ │ ▼ │ │ ┌─────────────────────────────────────┐ │ │ │ FileItemDisplay (动态组件分发) │ │ │ └─────────────────────────────────────┘ │ │ │ │ │ ┌────────┼────────┐ │ │ ▼ ▼ ▼ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ 图片 │ │ 视频 │ │ 文件 │ │ │ │ 预览 │ │ 预览 │ │ 预览 │ │ │ └────────┘ └────────┘ └────────┘ │ └─────────────────────────────────────────────────────┘
我们把每种上传方式抽象为一个统一的 action 对象:
const takePhotoAction = {
name: ‘拍照’, handlerFunction: async () => {
// 调用拍照用例... return imageUrl
} }
const albumAction = { name: ‘相册’, handlerFunction: async () => {
// 调用相册用例... return imageUrl
} }
const recordVideoAction = { name: ‘拍摄’, handlerFunction: async () => {
// 调用视频用例... return videoUrl
} }
这样有什么好处?
- 组件只管调用,不管实现 - 上传组件不需要知道"拍照"和"拍视频"有什么区别
- 可自由组合 - 页面想要哪些能力,就传哪些 action
- 新增能力不破坏现有代码 - 新增一个 action 即可
上传组件接收 actions,而不是自己创建:
type: Array, default: () => []
}, list: {
type: Array, default: () => []
}, max: {
type: Number, default: 10
} })
这就是最简单的依赖注入思想:我不制造依赖,我只是依赖的搬运工。
我们发现实际业务中有两种典型场景:
url[] 只关心文件
Labeling LabelingFileUploadRefact
{url, label, required}[] 需要标注说明
// Plain 模式:纯 URL 数组 ['https://xxx/1.jpg', 'https://xxx/2.jpg'] // Labeling 模式:带标签的对象数组 [ { imgUrl: 'https://xxx/1.jpg', imgExplain: '哈基米', isRequired: true }, { imgUrl: 'https://xxx/2.jpg', imgExplain: '南北绿豆', isRequired: false } ]
两种模式对应不同的数据结构,但对外接口保持一致。
根据文件类型动态渲染不同的预览组件:
function getComponentForUrl(url)
return FilePreviewComponent // 默认 }
预览组件的配置是这样的:
export const imagePreviewConfig = {
match: (url) => isImage(url), component: ImagePreviewComponent }
export const videoPreviewConfig = { match: (url) => isVideo(url), component: VideoPreviewComponent }
这就是配置驱动的设计:新增一种文件类型,只需要添加一行配置。
以拍照为例,完整的业务流程是:
调用相机 → 拍照 → 添加水印 → 上传 OSS → (可选)下载到本地
我们把这个流程封装成一个 UseCase:
export const createTakePhotoUseCase = () => {
const cameraService = createCameraAdapter() // 适配器
const takePhotoRefact = async (params, options = {}) =>
return url
}
return { takePhotoRefact } }
这样,业务逻辑被收敛到一个地方,后续改逻辑(比如水印样式)只需要改这一处。
底层调用的是原生能力,但各平台的 API 不一致怎么办?用适配器:
export const createCameraAdapter = () => , saveImage: async (url) => { return await native.saveImage(url) }
} }
如果将来换了一个原生框架(比如 uniapp),只需要改适配器,业务用例不用动。
业务页面使用非常简单:
import { useFileUploadActions } from ‘./hooks/useFileUploadActions.hook.js’ import { imagePreviewConfig } from ‘./components’
// 1. 获取 action(业务逻辑) const { takePhotoAction, albumAction } = useFileUploadActions()
// 2. 按需组合 const actions = computed(() => { return [takePhotoAction, albumAction] })
// 3. 传入组件
v-model="fileList" :actions="actions" :max="10" :customComponentMap="[imagePreviewConfig]"
/>
一行 v-model,搞定上传。
重构后:
- ✅ 代码复用 - 所有页面上传逻辑统一
- ✅ 扩展方便 - 新增上传方式只需添加 action
- ✅ 维护简单 - 改逻辑只改一处
- ✅ 体验一致 - 所有页面上传交互统一
回顾整个设计,我们用到了:
- 适配器模式 - 隔离原生 API
- 策略模式 - 动态选择上传方式
- 依赖注入 - 组件接收而非创建
- 配置驱动 - 动态渲染预览组件
但我想特别强调一点:我们不是为了用模式而用模式。
所有的设计选择,都是为了解决实际问题:
- 不用复杂的状态管理,因为场景足够简单
- 不用 IOC 容器,因为 Hook + Props 已经够用
- 不用过度抽象,因为当前复杂度完全可控
好的架构,不是用最"高级"的技术,而是用最"合适"的方式解决问题。
我们见过太多"过度设计"的案例——引入一堆框架和模式,最后发现业务根本不需要。适度设计,是在复杂性和可维护性之间找到的那个平衡点。
如果你有更好的方案,欢迎交流。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/280452.html