如何设计一个多端相册组件?

81 阅读5分钟

目前仅讲设计过程,源码待补充...

引言

在移动互联网时代,为了覆盖更广泛的用户群体并降低开发成本,跨端开发已成为主流。然而,如何在不同技术栈(如小程序、H5、React Native)之间保证 UI 的一致性、交互的统一性以及卓越的性能,是每个开发团队都必须面对的核心挑战。一套设计精良的跨端组件架构,是解决这一系列问题的关键。本文将以设计一个相册组件为例,回顾作者在开发跨端组件过程中的所思所想。

一、明确需求和目标

1. "多端"具体指的是哪些平台?

微信小程序、H5、React Native

2. 组件的核心功能有哪些?

  • 不仅支持图片展示,还需要支持视频播放
  • 支持左右滑动,需要考虑手势监听
  • 有tab作为指示器,需要和相册主体联动

3. 对性能和加载速度的预期如何?

  • 用于页面的首屏组件渲染,需要做到首屏秒出
  • 滑动不卡顿,不黑帧
  • 需要贴近原生性能
  • 支持懒加载,加载失败占位

4. 是否有特定的设计或交互要求?

  • 相册帧有高度变化,并不是固定的
  • 视频特定条件下(如wifi)会自动播放
  • 滑动到底部支持继续滑动跳转至二级页面

5. 是否有团队的技术栈偏好或限制?

偏向使用React和Taro,比较熟悉,上手快

二. 架构设计

1. 技术选型

维度推荐技术/框架决策依据
多端框架Taro + React满足多端(小程序、H5、RN)和技术栈偏好。Taro 的条件编译是实现三端原生组件隔离的关键。
轮播组件 (Swiper)三端原生组件 + Taro 条件编译贴近原生性能。小程序使用 <Swiper>,本质是小程序的swiper组件,H5 使用 <View> + CSS translate3d 灵活度高,RN 使用 <ViewPager>,原生性能好。
媒体控制统一封装 MediaView抽象图片/视频的加载、播放和暂停逻辑,实现离屏暂停播放互斥
性能优化LQIP/Base64 + 精确预加载解决“首屏秒出”和“滑动不黑帧”。首帧采用低质量占位图。预加载 N±pageRangeN \pm pageRange 的高清资源。
手势与交互原生组件事件监听 + 事件冒泡控制监听原生组件的滑动事件,在边界(最后一张)控制事件冒泡,触发“左滑进入看详情”的自定义事件。

2. 挑战与设计策略

核心挑战设计策略(解决方案)关键实现要点
首屏秒出渲染/加载分离 + 预加载OptimizedImage 仅在 shouldLoadtrue 时渲染真实 <img><Image> 标签。首图数据中包含 Base64/LQIP,确保第一时间渲染占位图。
相册帧高度变化动态样式计算与组件封装Carousel 监听当前 activeIndex。子组件 CarouselItem 根据索引应用不同的 CSS/Style 高度。由于高度不固定,需要确保轮播组件能正确适应,避免抖动。
视频特定条件自动播放网络状态检测 + 生命周期控制组件初始化时,使用 Taro API getNetworkType 判断是否为 Wi-Fi。如果满足条件且当前帧激活,则调用 <Video> 组件的 play() 方法。
滑动到底部跳转二级页边界手势事件监听监听轮播组件的滑动结束事件。如果判断当前索引为列表末尾,且滑动方向为继续向前(左滑/上滑) ,则触发一个 onReachedEndAndSwiped 自定义事件,由外部容器处理路由跳转。
Tab与主体联动单向数据流 (Props)MultiAlbum 维护 activeTab 状态,传递给 TabSelectorCarouselTabSelector 触发改变后,更新 MultiGallery 状态,驱动 Carousel 刷新数据(Key 变化)。

3. 组件架构设计

3.1 架构设计图

image.png

3.2 核心设计理念

3.2.1 容器/展示模式(Container/Presenter Pattern)

将组件的数据管理和逻辑处理UI渲染和展示分离,容器MultiGallery负责状态管理、数据获取和复杂逻辑,而展示组件TabSelectorCarousel只负责接受Props并渲染UI。可以使得展示组件,如TabSelector有更高地复用性,因为它不依赖任何内部状态或业务逻辑。

3.2.2 策略模式(Strategy Pattern)

封装算法,使得算法的变化可以独立于使用它的组件。Carousel是UI组件,它感知抽象的控制属性,如shouldLoadisActive,渲染策略被封装在OptimizedImageOptimizedVideo中,Carousel不需要知道图片是如何实现懒加载的,也不需要知道视频是如何实现互斥播放的,它只需要依赖shouldLoad这一抽象接口。我们可以随时修改或者替换OptimizedImage的懒加载算法,而无需修改核心的Carousel逻辑。

3.3.3 适配器模式 (Adapter Pattern)

Carousel 期望使用一个统一的接口,如 <GallerySwiper activeIndex={N} onSlideChange={fn} />适配器(三个平台文件)将这个统一接口适配到各平台的原生组件上,实现了跨端兼容性Carousel 只需要编写一次代码,就能与不同平台的原生组件无缝协作。

4. 目录结构

MultiGallery/
├── package.json
├── tsconfig.json
├── .babelrc
├── README.md
└── src/
    ├── components/
    │   ├── MultiGallery.tsx           # 1. 应用层:容器主入口
    │   ├── TabSelector.tsx            # 1. 应用层:Tab组件
    │   ├── Carousel.tsx               # 1. 应用层:轮播图组件
    │   └── core/                      # 2. 核心逻辑层
    │       ├── GallerySwiper/       # 3. 跨端适配层
    │       │   ├── index.tsx        # 适配入口
    │       │   ├── index.weapp.tsx  # 小程序实现
    │       │   ├── index.h5.tsx     # H5实现
    │       │   ├── index.rn.tsx     # React Native实现
    │       ├── OptimizedImage/        # 4. 媒体抽象层:图片优化
    │       │   ├── OptimizedImage.tsx # 图片组件(LQIP/Base64等)
    │       │   ├── index.scss
    │       │   └── index.ts
    │       └── OptimizedVideo/        # 4. 媒体抽象层:视频优化
    │           ├── OptimizedVideo.tsx # 视频组件(Wi-Fi播放等)
    │           ├── index.scss
    │           └── index.ts
    ├── utils/
    │   └── index.ts                   # 工具函数
    ├── hooks/
    │   ├── useCarouselState.ts        # 轮播状态管理hook
    ├── constants/
    │   ├── index.ts                   # 常量
    └── index.tsx                      # 组件导出入口

三、核心内容 -- 多端GallerySwiper的实现

待补充代码

四、一些难点与挑战

1. 复杂的交互

  • 手势监听:高度动态变化
  • 手势监听:滑动到底部加载下一页
  • 视频自动播放/暂停、互斥

2. 多端兼容及性能优化

  • 懒加载
  • 图片组件优化:加载性能
  • 视频组件优化:播放性能