组件化作为一种开发模式,其在代码复用,提高开发效率上的效果被广泛认可。组件化思想适用于移动端、Web 前端、PC 端、TV 端等多种类型的客户端和前端开发。
本文主要讲述爱奇艺知识 WEB 前端团队如何结合自身的业务特点,探索和实践了一套高效的前端组件化方案。
组件化:前端解耦和提效利器
前端业务发展过程中,代码体积会越来越大,业务的逻辑复杂程度也会随着迭代越来越高。
组件化的意义在于提效,交付的产物是可用的、直观的、可组合的业务形态。
目前行业内前端组件化按面向使用人群大致可分为 3 类:
面向运营人员:运营人员主要进行页面的创建、设计,组件类似乐高积木一样,拼凑起来,针对特殊的组件,可交给开发人员定制实现,实现一些偏静态或者偏固定功能(如抽奖、问答、支付等)的页面,各个大厂一般都有自己 Low Code 平台下类似的产品。
面向设计人员:设计人员主要参与页面设计,并直接将设计稿转换成前端代码,省去前端的开发过程,比如阿里的 imgcook,也可针对一些复杂逻辑,可交给开发人员进行二次开发。
面向开发人员:这也是最传统的开发模式,主要针对的是动态的页面,需要后端接口配合的场景,各个页面的组件通用,所以抽离出来公共组件供各个页面调用。
爱奇艺知识的组件化设计属于第三种,也即是面向开发人员而设计的。在开发层面,通过组件的复用,能够减少冗余代码、实现功能和业务逻辑的解耦,并且通过组件的拓展提高可拓展性、可维护性。
业务场景:项目多且独立、又要保持产品一致性
具体到组件的拆分,目前在业内并没有统一的标准,需要结合具体的业务场景,因为不同场景下组件拆分的力度、程度可能都有所不同。
爱奇艺知识 WEB 前端项目构成
从爱奇艺知识 WEB 前端项目构成可以看到,知识 Web 前端的项目很多,我们把项目按类型分开,每个分类下的项目都是独立的项目,完成其相关的业务功能,各项目是独立开发、独立部署的,并且项目间没有直接的业务关联。项目虽然多,但项目中都有业务相关的课程、讲师、店铺等实体,而这些实体的 UI 样式在各个项目中要基本保持一致,另外一些通用的 UI 元素,比如弹框、loading 等也要保证风格一致,因此为了产品体验一致性,同时提升开发效率,提高代码复用性,Web 前端需要进行组件抽离。
在这里需要简单说明一下,我们 Web 端组件化思路和知识移动端团队在组件化的思路是不太一样的,移动端更多的是从业务和功能的纵向进行组件划分,也可以称之为模块化,每一个组件或模块既包括 UI 又包括业务逻辑,比如播放器组件播控播放控制和 UI 样式,分享组件包括分享功能和弹窗,支付组件包括支付流程和收银台支付结果页;而 Web 前端的组件化更偏重 UI 层面,逻辑更多的是放在页面中,这也符合 Web 开发的特点,UI 元素复用性更高。
感兴趣的同学可以了解一下移动端组件化的设计思路,传送门在这里:爱奇艺知识移动端组件化探索和实践。
根据上述背景,知识 Web 前端进行了相应的组件化建设,目标如下:
1. 代码复用,提升开发效率
根据业务特点,横向和纵向划分组件,以组件为单位承接业务需求。虽然以 UI 组件为主,但对于相对独立的功能,也进行了纵向的组件抽取。
2. 组件独立开发维护,与各项目解耦
组件单独开发、调试、发布,供业务方调用,业务方只需专注业务本身。
3. 更方便的组件调用,更合理的路由跳转
组件调用需明确输入、输出参数,并对参数进行校验并给出合理错误提示;当各项目引用业务组件 涉及到跳转时,组件库自动决定是项目内(前端路由)跳转还是项目外(location.href)跳转。
4. 组件文档化,支持在线调试,降低组件使用门槛
组件库需要有完善的使用说明文档,支持在线调试,使业务方更加容易、便捷的使用。
整体设计
任何工具的设计都需要从实际的业务场景出发,回到我们的场景下,需要考虑如何管理项目间所依赖的公共组件资源变得非常重要,组件的设计既要符合业务方使用的便利性,同时也要满足组件自身开发的可维护性、可扩展性。
我们认为,组件化的关键在于组件的分层以及拆分,明确组件的层级以及对层级的定义对于组件化系统至关重要。下面是基于我们自身的业务,我们将组件进行如下分层(图示):
为了更好的理解,我们将层级之间的关系进行如下展示(图示):
由于 Card 组件由多个 Item 组件组成,所以 Card 组件与 Item 组件的关系如下:
card 组件
基于以上设计,我们简单总结一下,组件按类别可分为:基础组件、业务组件。
其中基础组件可分为:基础 UI 组件、基础工具组件;业务组件可分为:item 组件、Card 组件、区块组件、功能组件、页面组件。
基础组件
基础工具库:这是一组 JS 库,无 UI 界面,包含项目所使用的基础功能,如:网络请求、页面埋点、异常上报、与 Native 交互的 CallNative,以及一些常用 utility 工具等等。
基础 UI 组件:包含了:文本、图片、按钮、布局、loading 加载、toast 提示等常用的 UI 组件。
业务组件
Item 组件:业务组件的最小单元,比如:列表中的一项,宫格中的一项,金刚位中的一项、轮播图中的某张图片等等。业务方可引入所需的 item 组件或组合 item 组件来完成页面的展示。
Card 组件:这是一套服务于 Card 化所对应的一套组件,这里简单介绍一下 Card 化:后端返回的是一套基于 Card 的数据结构,每个 card 数据与前端某一个 UI 组件一一对应,通过对数据的配置来实现对前端展现的控制。Item 组件也可以属于 Card 的一部分,由 Card 组件组合多个 Item 组件来进行界面的展示。
区块组件:是为了完成某一个区块的 UI 展示,而抽离出来的组件,供业务方使用,比如:评论组件、评价组件等。
功能组件:是为了完成某一项具体的业务功能而抽离出的组件,比如:播放器组件、发布器组件、分享组件等。
页面组件:也叫路由组件,这是基于微前端所抽离的组件,我们的某个页面也可以当做组件,从而被其他项目所使用。
技术实现
由于基础组件、区块组件、功能组件已经是非常通用的设计,这里我们重点介绍一下业务组件中的 Card 组件、Item 组件。需要注意的是,不管是基础组件还是业务组件,我们在设计之初都需要让其符合以下设计原则。
设计原则
我们的组件设计原则需满足如下要求:
需要统一技术栈,保证组件在同一技术生态。
单一职责,一个组件只专注做一件事,且把这件事做好。
追求无副作用,输入一但确定,输出就是固定的。
可配置,一个组件要明确它的输入和输出分别是什么,同时入口处检查参数的有效性。
粒度适中,划分粒度的大小需要根据实际情况权衡,太小会提升维护成本,太大又不够灵活和高复用性。每一个组件都应该有其独特的划分目的的,有的是为了实现复用,有的是为了封装复杂度 实现业务清晰的目的。
适当的包体大小,便于页面快速加载。
完善的使用说明文档。
数据协议
在项目初期,我们的 H5 移动端首页:https://zhishi.m.iqiyi.com 是基于后端的 Card 化数据,由后端数据选择前端的 Card 组件、Item 组件来进行页面的展示,在这种场景下,我们的 Card 组件、Item 组件首先被设计出来,与此同时我们设计了一套前端数据协议(可以理解为数据格式定义),后端 Card 数据经过前端协议后,转换成前端的 Card 数据结构,在此数据结构的基础上进而抽离出 Card 组件、Item 组件。
后来发现我们大量的页面并非基于 Card 化数据,而是基于不同的 API 、不同的数据结构,如何让这些页面也复用我们的 Card 组件、Item 组件呢?为此我们需要不同的转换器,将后端的接口数据根据前端数据协议转换为 前端的数据格式(如下图),这样无论后端接口数据如何改变,我们只需更改转换器的逻辑,而无需更改前端组件代码,进而复用我们的 Card 组件、Item 组件。
UI 实现
在具体的 UI 实现方面,我们拿下图中我们的业务 UI 组件库内常用的两个 Item 举例:课程列表 Item、课程宫格 Item 。
不难发现 Item 组件主要是由 图片、文本组成的,而图片、文本恰好又属于我们的基础 UI 组件库范畴。
图片组件,是在保留原生`img`的特性前提下,支持懒加载,自定义占位、加载失败占位、图片获取策略(webp or jpg);
文本组件,支持 单行、多行截断。
所以我们在编写 Item 组件的时候只需组合图片、文本组件即可,这样后期随着我们组件数量增长,当我们开发其他 Item 组件的时候会变得非常轻松,效率也得到极大提升。
还有一点需要注意的是:可能我们的组件需要同时满足在移动端手机、平板、 PC 端的正常展现,这样就会涉及到布局以及屏幕适配的问题,同时也涉及到设备旋转带来的屏幕尺寸动态变化的问题。所以在组件在设计的时候 宽、高不能定死,而是引用方设定的,同时我们需要在组件内兼容不同屏幕的适配样式,也可以分别导出不同屏幕的适配样式文件供引用方使用。
构建工具
由于基础工具库 是一组 JS 库,所以我们使用 rollup 构建,它是一个高效的 library 模块打包器,可以使我们的组件更加轻量、简洁,同时生成 es, umd 格式文件供前端调用。
UI 组件库,我们使用 VUE 作为前端技术栈,使用 Webpack 进行构建,业务组件项目支持导出多个组件入口,支持业务组件按需加载(如下所示)
组件支持按需加载就意味着,各组件入口需要单独打包,这会涉及到各入口的公共资源重复引用的问题。
我们可以使用 @babel/runtime,@babel/plugin-transform-runtime,设置 core-js 为 false,做大限度减小 babel 转换的包体。
使用 babel 命令打包 JS 文件(不使用 webpack),减小 JS 包体。
使用 webpack externals 将第三方依赖,以及每个组件的公共引用提出来,通过 require 的方式去引用,而不是重复打包到每个组件内。
技术文档
我们的组件库开发完成,并不意味着组件化这件事就算做完了,一个良好的组件库往往需要有一个良好的文档说明,这样会降低团队成员的沟通成本,所以基于 storybook 我们搭建了 UI 组件的文档站点,比如下图的基础 UI 组件库文档,可动态展示最新的组件版本,同时支持 UI 组件在线调试,更能直观的表达组件样式与交互,增强了组件的易用性。
总结
前端组件库一般是放到公司的私有仓库上,通过 npm 进行维护的,使用方需要 引用并打包到自己的项目内才能使用。随着 webpack5 module federation 的流行,也可以将组件当作微模块单独发布,各使用方可通过动态 load module 的方式进行引用。
由此也可以看到前端组件化的优势:可以很大程度上降低系统各个功能的耦合性,这对前端工程化及降低代码的维护成本来说,是有很大的好处的。
爱奇艺知识 WEB 前端组件库经过不断的完善和迭代,基本实现了组件化的全部目标,组件通过独立开发、测试、发布,使得各业务项目只需关注本身业务,组件得到充分复用,目前组件化已成功运用到各个业务项目中。
组件化并非一蹴而就,而是一个持续的过程,比如:随着业务组件会变得很多,业务组件需要支持业务方按需加载,同时需要考虑组件的包体大小,不能因组件包体过大导致页面加载过慢;业务组件应该通过类似 Jenkins 工具统一发布,发布前应进行自动化测试,以完成很好的测试覆盖等等。
当然在实际开发工作中会有许多不理想的情况,比如一个小项目没有那么多重复的代码,比如设计团队不认可组件化等等,所以组件化需要考虑具体的业务场景,在这个前提下设计出符合我们自身的组件化系统才有必要。
本文转载自:爱奇艺技术产品团队(ID:iQIYI-TP)
原文链接:爱奇艺知识WEB前端组件化实践
评论