猫耳大型活动提效——组件低代码化

1. 引言

猫耳前端在开发活动的过程中,经历过传统的 pro code 阶段,即活动页面完全由前端开发编码实现,直到 2020 年接入公司内部的低代码活动平台,满足了大部分日常活动的需求,运营可自主配置活动并上线,释放了相当一部分的开发人力。不过,此时的方案仍然无法很好地服务大型直播活动场景,比如年度 S 级的直播活动,这类活动赛程多且持续时间长(可长达两周),页面和组件都包含多种状态,运营难以配置出大型直播活动的所有需求,故此阶段的大型活动仍然完全由开发编码实现,需要占用较多人力,但此类活动从数量上来看连 1% 都不到。 鉴于我们已在日常活动中积累维护了相当多功能的低代码组件,如何复用已有的低代码组件来更快实现更多活动玩法就成了一个值得研究的问题。此外,大型活动从筹备到结束,时间长达数月,而密集开发阶段可能只占其中的一个月,密集开发结束后的长尾需求又该如何优化提效?接下来本文将介绍猫耳前端在活动场景的低代码探索经验,以及最终稳定的开发模式。

2. 场景分析与问题识别

2.1 不同场景的开发方案

把时间先拨回 2022 年,当时我们已有一定的平台使用经验,从技术角度将活动划分为两大类型,根据业务需求采取不同的方案:

2.1.1 配置类页面(Low Code + No Code)

此类活动经产运及技术负责人评估后,若无需开发参与则由产运同事自助配置并上线。若需要开发参与,则视情况修改组件或开发 JS 或 CSS 补丁代码来实现,沉淀下来的代码也可用于后续的类似活动。

2.1.2 开发类页面(Pro Code)

在一些玩法较为复杂、活动状态多的场景,配置页面的难度可能会呈指数级上升,当时平台也并不具备完善的预览能力,所以此类活动几乎完全由开发负责实现(部分页面可能会跳转到配置类页面)。虽然工作量较大,但简单来说就是根据设计稿、产品需求文档、接口文档这几个关键输入来实现活动页面,在流程上并没有太多可说的。

2.2 问题与尝试

回到引言的问题,在讨论更具体的解决方案之前,我们先看看在经过几次大型活动的 pro code 开发后发现了哪些问题,在前期又做了哪些尝试:

2.2.1 活动规则变更效率低

由于活动页为了保证用户体验会集成较多的玩法规则,由于运营无法在活动的密集开发阶段一次性给到准确无误或完全不变的规则说明,在活动上线前,前端需要持续配合更新页面内的活动规则,有时候是换图,有时候是调整文字描述,改动的发布流程较长且影响开发测试效率。

前期尝试:比较泛的解决方法是将前端写死的切图改为从静态资源服务中获取,我们也确实在个别活动中尝试约定过资源路径,产运将资源上传到特定路径后,开发使用约定好的路径来加载资源,但此方案并没有很好地利用现有的低代码平台(如:配置间距、管理资源版本),也存在额外的上手成本和维护成本。

2.2.2 部分常见的需求处理效率低

相似需求总是需要走开发上线的流程,导致开发人力总是紧张,且效率难提高,如:

  • 每次活动都会设计新的榜单样式,这类换皮工作对开发来说耗时且枯燥,影响其他需求的开发进度
  • 直播活动结束后,开发需要按业务流程固化榜单数据,每次都需要将服务端导出的数据替换到代码中,然后再走一遍上线流程

该问题在前期并没有想到很好的处理方法,直到后续复用低代码组件才得以解决。

2.2.3 重复开发组件

前端在大型活动中存在重复开发组件的情况(如:直播榜单组件)。榜单在直播活动中是标配组件,但大型活动页面状态较为复杂,无法直接通过低代码平台配置,导致这类场景我们一直没能复用运营配置的组件,于是又额外开发维护了一套 pro code 场景的榜单组件。

前期尝试:将配置了单个 low code 组件的页面通过 iframe 的形式载入页面框架内,也确实满足了一些活动需求,但同时也发现了其局限性:

  • 组件若涉及弹窗和遮罩,由于弹窗和遮罩都是基于 iframe 页面插入的,难以直接基于宿主页面居中摆放弹窗,遮罩的处理也比较复杂
  • 页面之间可能存在跨域的情况,个别需求得花心思解决
  • iframe 页面完整加载较慢,会重复请求宿主页面加载过的资源,部分请求也并非必要

以上提到的几个痛点,不仅影响我们每次活动的交付效率,也使得前端团队整体的资源利用率较低,前端团队总是处理类似需求得不到成长,也无法推进其它业务需求。

3. 解决方案与实施

秉持着不写新代码就没有新 bugDRY 的开发原则,作者在经过几次 pro code 活动后开始思考:是否有方案可以解决以上问题,让我们可以有精力去做其它更加价值的工作?

3.1 远程组件

2022 年 10 月,首个接入B站远程组件能力(内部名:片段加载器)的猫耳直播活动上线,本次活动在 pro code 页面框架的基础上加载了运营配置的低代码榜单组件、弹窗、活动规则等内容,解决了第一部分提及的所有问题。

3.1.1 原理

顾名思义,远程组件依赖于服务端的组件数据,其包含三大数据:组件 JS 资源、组件 CSS 资源、组件 JSON 参数(运营配置)。组件数据的拉取、组件的渲染等工作,则由客户端的加载器提供支持。

前端研发人员在 pro code 页面框架内集成加载器后,只需传入约定好的页面片段 ID,加载器即可完成页面片段的拉取工作,并复用 low code 页面的渲染器完成渲染工作。

3.1.2 项目工作流

上述编码细节转变的背后,更深层次的转变其实是「开发类页面工作流程」的转变,以及项目人员的工作职责变化。

从新的流程中不难观察到,前端能够在设计稿尚未交付的情况下就提前介入 ,设计一些适合低代码化的功能模块,然后转交给运营配置使用。前端不仅能够在更专注业务逻辑的情况下提前交付运营也在复杂活动中获得了调整和发布的能力(仅限于部分功能模块),一些运营侧的调整也不必再走前端的发布流程,减少各端沟通返工的情况,项目的整体效率更高。

回到第一部分的具体问题来说,技术侧从以往活动中识别出后期需求、维护需求、效率问题后,可以借助低代码平台将流程功能化,从而实现更顺滑的交付效果。

3.1.3 接入前后的代码对比

旧的代码实现

为了更好地理解效率提升的效果,让我们先看一个典型的旧代码实现示例。这段代码展示了如何编写一个直播榜单组件,一个需要定制的 pro code 榜单通常需要 100+ 行的 CSS 代码,这也意味着前端要等设计资源完全 ready 后才能进开发(给设计也带来了压力),而在活动中需要定制的榜单往往不止一个,且每次活动都需要开发。在活动 DDL 明确的情况下,很容易给前端造成任务堆积,人力不足的问题。

新的代码实现

现在,通过采用远程加载低代码组件的方法,我们大大简化了这个过程。下面是一个使用远程加载低代码组件的示例代码。前端只需一行代码即可加载运营配置的 low code 榜单组件,无需编写繁琐的 CSS 代码,设计同事也可以调低榜单模块的优先级,优先完成其它高优的设计任务。

以下是直播榜单组件在后台系统中的界面截图,展示了运营人员如何通过界面配置而不是编写代码来完成同样的任务。

3.2 接入方案

以下是猫耳前端在接入公司内部低代码平台过程中积累的接入方案。

3.2.1 Pro Code 页面能力对齐 Low Code 页面

为确保 low code 组件能够在 pro code 页面框架内正常运行,我们将一些页面的基础能力封装到了单独的包内,使得不同环境都能够获得一致的效果。

基于 RxJS 实现组件间的数据共享与事件通信

在 low code 页面中,我们基于 RxJS 的 BehaviorSubject 实现了页面级别的数据共享方案,各组件可通过订阅 window 上挂载的 BehaviorSubject 数据流,及时获取全局数据(如:活动状态)。

而在 pro code 页面框架内,在综合考虑开发成本、性能、隔离性(不考虑沙盒环境)这几点后,所以我们直接在页面框架内复用了 low code 页面的初始化流程,确保各组件能够直接接入页面。

内置 Low Code 环境组件,优化加载速度

在 low code 页面中,我们用于初始化页面环境的组件本身也是个 low code 组件,按流程来说,在 pro code 页面内使用时也需要等待加载器的拉取渲染时间,而其余组件则要等初始化完成后才能正常渲染,所以存在一定的阻塞情况。

优化前的默认实现

以下是优化前的页面初始化时序图。

优化后的实现

优化后的时序图已经在远程组件的 3.1.1 原理小节贴过了,可以返回查看。

简而言之,我们将 low code 页面的环境组件直接集成到了 pro code 页面框架内,跟随页面一起构建发布,并且屏蔽了加载器对于环境组件的拉取和渲染逻辑,使得页面能够更快完成初始化。

Pro Code 页面维护所有 Low Code 组件配置数据,支持「合并同类组件的请求」

对于传统的 pro code 页面来说,如果出现需要请求同一接口的同类组件(如:多个音频、多个主播的信息),我们可能只需要在请求时带上 ID 数组即可,但对于 low code 页面来说,为了保证最灵活的制作能力,我们不一定会封装固定布局的组件,更倾向于封装原子组件或者较为基础的业务组件,所以运营拖入页面的可能是一个个独立的组件(同类组件,但配置了多个),每个组件都单独配置了 ID,如果不做处理的话可能会并发出非常多的请求。

对于这种场景,我们以前采取的方案是:在该 low code 组件的 JS 资源被插入执行时(此时渲染器还没有渲染组件),读取页面内配置的所有同类组件,合并配置后统一发出请求,然后借助 rxjs 缓存接口响应数据。等到组件被渲染器实际渲染出来后,再订阅该 rxjs 数据源,实现合并批量请求的目的。

如果现在需要在 pro code 页面框架内实现该场景,对比 low code 页面我们缺少了直接可用的完整组件配置,需要等所有片段都加载完才能拿到完整组件数据。不过这也不难解决,基于已有的 rxjs 数据共享流程,我们只需要对这类组件稍加改造,将直接获取所有组件的配置数据改为订阅所有组件的配置数据。当页面框架逐个获取到组件信息后,逐个塞入 rxjs subject 的组件数据列表,然后组件侧结合 rxjs debounce 操作符(避免频繁请求接口),按一样的流程请求接口获取数据即可。

以下是简化后的请求远程组件并渲染的核心流程。

typescript 复制代码
fetchSegment(pageId) 
  .then(async (res: any) => {  
    if (!res || !containerRef.current) {   
      console.error('segment load failure')    
      return  
    }
    const { info, render } = res 
    if (!loadedPageIds.includes(pageId)) {  
      loadedPageIds.push(pageId)   
      const newComponentsMap = makeComponentsMap(info.configure?.pageData) 
      window.MissEvanEvents.next((prev: any) => {   
        const mergedComponentsMap = Object.entries(newComponentsMap).reduce((acc, [key, value]) => {        
          acc[key] = (acc[key] ?? []).concat(value)      
          return acc    
        }, prev.componentsMap ?? {})    
        return {   
          ...prev,     
          componentsMap: mergedComponentsMap,      
        }
      }) 
    }
    render({ container: containerRef.current, needRenderEnvComponent: false }) 
  })
  .catch((e: any) => {  
    console.error('segment load failure', e) 
  })

3.2.2 规范代码块开发模式

上文在配置类页面已经提到过代码块,代码块由 JS 和 CSS 文件构成,在特定时机插入页面执行,主要执行一些 dom 操作或添加样式。在 pro code 页面框架内集成代码块时我们也做了一些优化工作。

由于代码块的效果依赖 JS 的插入执行,在按需渲染的场景会存在重复插入执行的情况,若忘记清理副作用也更容易出现问题(如:事件泄漏、定时器无法终止)。为优化该问题,我们在 JS 中临时加入了一些代码来检查逻辑是否执行过,以及定时任务是否需要执行等操作,避免出现不必要的问题,并在后续基于 document.currentScript 设计了一套代码块的生命周期函数,用于规范常用代码块的参数、执行、清理。

scss 复制代码
// 代码块被插入时的定制逻辑
function mount(dependency) {}

// 代码块被销毁时的清理逻辑
function unmount(dependency) {}

ContextManager.initLifecycle({ mount, unmount })

我们在 24 年 3 月份将该方案提交给活动中台并进行了比较密切的几次讨论,平台综合各个业务的需求在 24 年 9 月份上线了更加通用的平台级别的代码块开发工具,后续也仍在持续迭代。

4. 成果与效益

4.1 释放前端开发人力

鉴于活动玩法并非一成不变,不同开发者的能力也有区别,单纯的代码量或是工时人天数据并不能准确体现出低代码带来的提升,顶多用于内部预估未来活动的工作量,故以下统计的是各个具有代表性的活动低代码程度,以及组件和代码块的复用情况。由于部分需求比较零散,实际成果可能会比下表更多一些:

从表中可以发现前端自主开发了非常多的代码块,尤其是某些乍一看随便写写也挺快而且没啥复用价值的功能模块,我们也选择将其低代码化交由运营配置。这样做的好处是:不仅大部分内容可以实现脱离设计稿提前开发,还可以摆脱许多后期需求和维护需求,对前端开发来说是一个非常有利的提效模式。

从岗位职责的角度来看,也可以认为代码块开发模式(高频)是在产品正式介入组件设计前(较低频),前端提前将功能模块进行了一部分的抽象设计,便于后续将该功能开发成低代码组件。在代码块开发模式下,运营侧的体验可能弱于组件模式(平台层面已在优化这个问题,如:降低配置难度,提高开发效率),但是仍能够保持一定的交付效率,以及用户侧一致的体验。

4.2 版本管理颗粒度进一步细化

pro code 页面接入 low code 组件后,也带来了一个副产品------页面被打散,版本管理的颗粒度更细了。我们也亲身经历过线上活动出现紧急问题,产运直接调整发布,快速缩小影响范围然后开发再排查修复的情况。远程组件让我们在应急处理线上问题时又多了一种手段。

5. 结语

以上是猫耳前端对于活动低代码场景的探索和经验总结,欢迎在评论区继续讨论,一起挖掘业务提效的方法。在此感谢猫耳前端组以及 EVA 平台组一起参与建设的同事们,特别感谢 EVA 平台组的璇儿、琥珀草、谷风长道、小白白川、V、Fryderyk 等同事对我们提供的大力支持。对 bilibili 活动中台系统设计感兴趣的小伙伴可以继续移步查看这篇新活动平台建设历程与架构演进

-End-

作者丨Helson、Rui

相关推荐
低代码布道师4 小时前
加油站小程序实战教程09显示站点信息
低代码·小程序
MuShan8 小时前
JeecgBoot 一年使用心得
低代码
HUIBUR科技1 天前
用低代码平台集成人工智能:无需专业开发也能实现智能化
低代码·ai
NocoBase2 天前
2025 年开源替代方案为何正在取代 OutSystems?技术自由度与成本优势深度解析
低代码·开源·开发工具·无代码·outsystems
低代码布道师3 天前
加油站小程序实战教程07城市管理
低代码·小程序
阿三08123 天前
低代码平台的后端架构设计与核心技术解析
低代码
_xaboy3 天前
FcDesigner页面样式错乱/功能不正常解决办法
vue.js·低代码·开源·动态表单·表单设计器
有颜有货4 天前
数字化工厂实施时,通常都会碰到“系统对接困难”的问题,这个用低代码能解决吗?
低代码
db_murphy4 天前
知识篇 | 低代码开发(Low-Code Development)是个什么东东?
低代码