阿里妹导读
本文讲述新版钉钉协作从产品能力升级背后的前端技能支撑和技能视角性能体验优化及稳定性培植两个方面讲述协作前端培植的过程。
钉钉新版协作Tab作为千万级访问量下前端新运用,从我主导前端迭代至今已经有大半年,面对大流量下的高性能和稳定性的压力、繁芜前端交互的设计实现、前端技能视角结合业务的高可扩展性架构设计寻衅,从0到1完成了三个大版本的迭代,联合客户端和小程序容器完成性能及稳定性方案落地、跨业务线承接了20多种卡片类型,供应了稳定体验的场域做事,同时在研发机制保障下做到整年0客诉和0回滚。本篇将讲述新版协作从产品能力升级背后的前端技能支撑和技能视角性能体验优化及稳定性培植两个方面讲述协作前端培植的过程。

一、背景
比较旧版本以运用为中央的钉钉协作Tab,新版本产品设计理念发生变革:组织管理与引发个体高下共生。协作以个人为中央,聚合与“我”有关的待办、审批、日程、文档等由钉钉各一、三方运用产生的事宜,帮助用户更高效地处理与我有关的事宜,得到更高效的协作体验,协作通过聚合用户的协作事宜,帮助个人提效,进而提升组织效率。
1.1 新版协作Tab心智
协作Context,与我(员工个人视角)有关的协作信息消费处理中央。
1.2 业务目标
新版协作信息流部分的点击率,目标是好比今的协作Tab卡片部分的点击率提升100%。逻辑是验证新的韶光流比之前的运用Widget,对付用户来说更“有用”。(目标之一)
这里为什么要讲目标,便是在产品能力培植的过程中要把自己的目标和业务目标关联起来,然后交融贯通到前端培植之路中做出沉淀,从而表示因“前端”而带来的业务结果加持。
二、产品能力升级背后的前端技能支撑
协作前端进化论:
新版协作进化进程
before:旧版本
以运用为中央
after:协作新版
初版,以韶光为中央,强调顺序。
第二版本,以事宜为中央,强调操作。
第三版,以人为中央,强调关系。呼应主题,事情版“今日头条”
很多时候我们难以从产品最初想到完全产品终态,但是如果我们用产品本身的目标和用户代价反推产品的设计和技能实现的时候我们就可以做出相应的改变。
2. 1 协作前端主体架构设计之feeds流卡片动态化
卡片初版到第三版迭代培植过程中通过验证有效性及代价递增持续做出产品上的调度和优化
前端设计实现上的思考
在做初版卡片实现的时候就和产品对过后续产品能力上的方案,卡片UI内容的新增删除这里都是更新频率比较高的,同时卡片的逻辑层面的功能也是须要不断培植,比如操作按钮、卡片点击事宜、点击后的打开办法、卡片的删除置顶等功能等。在这个背景下我决定这样设计:将卡片频繁变更的UI层和不会变更的卡片功能逻辑层抽象出来分开培植,这样即能担保卡片功能持续培植的过程中不须要频繁变更,又能知足卡片UI持续迭代。
将卡片UI层进行布局维度的分层设计
前端组件构造化事宜卡片
//卡片引用<common-card-box onClickCardBtn="onClickCardBtn" componentData={{feedItem.componentData}} swipeIndex={{swipeIndex}} childrenFeed={{feedItem.children}} //卡片内部组件拼装<div> //卡片头部第一层 事变来源和事变韶光 <common-card-head userInfo={{componentData.userInfoVO || {} }} totalBadge={{totalBadge}} //红点能力覆盖 />//卡片内事变标题 事变描述 <common-card-content props={{...props}}/>//互动卡片SDK小程序插件引用条件渲染 <component id="{{componentData.flowId}}" componentProps="{{pluginComponentProps}}" is="dynamic-plugin://5000000002721132/card" a:if="{{ isReady && componentData.isPlugin }}"></component>// 卡片补充3C推举内容 <common-card-recommend props={{...props}}/>// ButtonList 卡片相应动作 <common-card-buttons props={{...props}}/>//卡片折叠 递归引用 此处不须要UI变更<view a:if="{{childrenFeed && childrenFeed.length > 0}}" class="children-contain"> <common-card-box a:for="{{childrenFeed}}" componentData={{item}} isReady={{isReady}} ut={{ut}} from={{from}} onClickCardBtn="onClickCardBtn"/> </view></div>
这样有个好处便是代码构造清晰直接可以从代码脑海构思出布局,无论你卡片内容如何变革,我都可以通过调度对应的组件快速支持产品上做出的调度需求。
运用处景:目前钉钉内20+种通用化卡片展示场景支持。
卡片操作逻辑层能力培植(以不变应万变)卡片内容操作:jsapi类型(发起Ding催办等) 跳转类型(半屏等卡片详情)
卡片本身操作:卡片删除、卡片置顶,卡片折叠展开
更新操作:点击操作卡片后的卡片内容更新,如日程接管后变成已完成。
这里须要把稳的是卡片功能培植过程中业务结合代码的可扩展性,纵使卡片UI怎么调度,但是功能逻辑层是不须要变更。在协作运用的发起协同要求到协同结束的全体过程中,通过分角色分场景的信息设计,在协作流中通过发送或沉淀信息的办法,在关键协同节点向用户供应最有代价的协同信息。
删除、更新、置顶等场景下 合营小程序 $spliceData 前端本地列表无感更新。
modifyFeedFlowsCommon( flowId: string, type: string, targetName: string, feedFlows: IComponentInfo[], newFeed?: IComponentData) { // step1 找到对应卡片在协作卡片或子卡片(折叠卡片)中的index const feedFlowsIndex = this.findFeedFlowsIndex(flowId, feedFlows); // step2 通过小程序供应的$spliceData函数进行局部更新 if (feedFlowsIndex) { this.modifyData(targetName, type, feedFlows, feedFlowsIndex, newFeed); }}, modifyData( targetName: string, type: string, feedFlows: IComponentInfo[], feedFlowsIndex: IFeedFlowIndex, newFeed?: IComponentData) { // 分发操作事宜,局部更新 this.$spliceData({ [targetName]: [parentIndex, 1, newFeedItem], }); }};
2.2 协作场域互动卡片SDK能力集成
为什么要集成互动卡片:这里可以参考协作做事端架构设计的推拉模式,假设我们想要提高钉钉事情台运用的渗透率将更多和用户有关的繁芜场景都流入到协作里,并且担保同样的卡片在其他地方也能同时投放,互动卡片是一定的。
协作中有两种卡片类型,第一种是上述的标准业务卡片,第二种是可以支持钉钉跨场域投放的互动卡片。
钉钉标准互动卡片是钉钉通用的卡片类型,会针对不同的投放场景进行能力与样式的自适应,确保同一个卡片模板在谈天、群聊吊顶、协作、事情台等场景中拥有同等的利用体验。
钉钉互动卡片搭建链路+投放链路示意
钉钉可交互互动卡片详情请看开放平台文档:https://interactive-card.alibaba-inc.com/
互动卡片在协作中的运用方案-业务套壳互动卡片卡片本身由两部分组成:数据+模板。数据依赖同步协议来实现动态更新,而模板则是通过小程序包的动态下发和更新机制来实现动态更新的。
协作内部实现以及协作业务场景的分外性数据差异:卡片数据由两部分组成,业务本身供应的数据源如上图中的事变标题及事变描述,和协作自己的数据源3C推举情由以事变来源和韶光。
在这个差异根本上我们采取业务卡片嵌入互动卡片的形式进行渲染,我称之它为业务套壳互动卡片,这样在知足业务场景的同时能够将互动卡片的内容最大程度的多场域化运用。
内部实现第一步加载小程序小程序包,这表示在当客户端在加载一个卡片模板时,实质上它是在加载一个小程序包,小程序包里面则包含了卡片模板资源。
// 互动卡片插件初始化 initCard() { try { if (dd && dd.loadPlugin) { dd.loadPlugin({ plugin: '5000000002721132@', success: (res: any) => { this.setData({ isReady: true }); }, fail: (err: any) => { logErrorUser('插件加载失落败',JSON.stringify(err)) }, }); app.globalData.hasInitCard = true; } else { logApiError('dd.loadPlugin', Date.now(), {}, 'dd.loadPlugin load fail', 'jaspi') } } catch(e) { logApiError('initCard fail', e); } },
第二步小程序插件引用卡片SDK
<component id="{{componentData.flowId}}" componentProps="{{pluginComponentProps}}" is="dynamic-plugin://50000000027211321/card" a:if="{{ isReady && componentData.isPlugin }}"> </component>
同一张互动卡片投放在不同的场域,如何实现区分?
卡片组件 context 增加 trackingRuleModel 入参。 在渲染过程中透传这个参数。
export interface IRenderContext { / 场域标识,谈天为 'im', 吊顶为 'topbox' 等, 未定义的场域可以先填写 standard / bizScene: string; / 多场域埋点规则 / trackRuleModel?: ITrackRuleModel; / 多场域埋点数据源 / trackSourceDataModel?: Omit<TrackSourceDataModel, 'extension'> & { extension?: never; };}
有没有一种可能协作只作为一个场域,你看到的协作feeds流完备由互动卡片来承接?可以。
互动卡片在协作中的运用番茄表单、云上管车等ISV运用,与正常卡片融为一体,浑然天成,毫无违和感。
未来更多的一方二方三方互动卡片都可以流入到协作中,乃至协作可以直接提升事情台运用的渗透率。
番茄表单互动卡片在协作流
2.3. 算法推举序接入
为什么要接入算法:1.利用钉钉现有的数据中台和算法团队的协同关系模型对用户供应个人视角下的高代价事宜处理卡片。
2.呼应协作业务的目标,提升点击率,也便是验证协为难刁难用户越来越有用!
协作前端算法推举序链路设计
前端设计实现:推举序列表由两段组成,待处理 和 其他,在有了上面讲过的卡片模型设计实现后,实质上对前端来说这里的事情量只是对推举序列表的拼接,UI层和逻辑层所有交互均已被覆盖。
待处理(主要内容):对用户强干系的处理卡片,比如未完成的待办、三条后开始折叠。
其他(可能感兴趣的):基于算法协同关系模型以及前端供应的行为埋点数据由算法中央统一产出,交互上跟随推举序列表下拉加载。
2.4. 协作PC 跨端产品能力补齐
为了补充协作在桌面真个体验空缺。依赖多端协同的运用,如文档,须要协作支持桌面端后才有可能将运用的协同从运用内迁移到协作。
协作PC端
结合了协作在移动真个技能沉淀,2月尾我们通过10天韶光快速将协作PC端协作完全交付。
本篇重点讲述协作移动端培植过程,PC真个前端设计方案实现这里先不做陈述,后续会另出一篇ATA来进行完善的方案讲解全栈化如何落地协作PC,敬请期待。
2.5. 数据运营能力培植
APN统一推送能力培植运用处景举例:钉钉年度报告承接:
APN推送钉钉年度报告承接
在接到需求的第一韶光培植了通用的APN推送链路方案:利用channel建立客户端到前真个通信通道,在客户端对钉钉统一跳转协议的监听下推送给前端跳转的来源信息,同时前端通过订阅的监听事宜做干系的业务逻辑操作。
import { getChannel } from '@ali/dingtalk-jsapi/plugin/uniEvent';export function ddSubscribe( channelName: string, eventName: string, handler: (data: any) => void, useCache = true,) { try { const channel = getChannel(channelName); channel.subscribe(eventName, handler, { useCache }); } catch (e) { logApiError('ddSubscribe fail',safeJson(e)); }}// 客户端开启订阅统一跳转协议监听事宜,并建立两端通道ddSubscribe('channel.jumpAction.switchtab', 'cooperate_cooperate', (data) => { if(data?.data.from==='nianzong'){ sendUT('Page_Work_New_Year_Summary'); openLink$({url:yearSummaryUrl||'https://page.dingtalk.com/wow/dingtalk/default/dingtalk/yearsummary?dd_nav_translucent=true&wtid=yearsummary__xiezuo'}) } });
协作顶部运用区
运用区为什么要折叠
受全局切换组织、运用快捷入口吸顶、信息流工具栏吸顶等多个缘故原由影响,协作信息流自身的内容展示区域,不敷整体页面高度的1/2,新版的信息卡片,增加信息量的同时也增加了卡片高度,以是要用折叠办法将屏效比提高。
运用区展示逻辑:纯前端实现,此处无做事端交互
1.联动设置页的拖拽自定义顺序展示。
2.每个入口均由一个灰度key统一掌握在协作中的展示,同时针对专属钉大客户定制降噪对运用入口进行过滤。
3.展示前通过客户端JSAPI进行红点数量设置。
//灰度key获取到联动降噪红点设置统一管理export const getGrayValueFromCacheByKeys = async (keys: KeysType[] = []) => { const result: any[] = []; const promiseAllArr: Array<Promise<any>> = []; const promiseAllArrKeys: KeysType[] = []; keys.forEach((key) => { if (cacheGrays[key]) { result.push(cacheGrays[key]); return; } const defaultGrayKeysConfig = allGrayKeys[key]; if (!defaultGrayKeysConfig) { result.push(true); return; } if (!defaultGrayKeysConfig) { result.push(!!defaultGrayKeysConfig); return; } promiseAllArrKeys.push(key); promiseAllArr.push(grayLemonFnFactory(...defaultGrayKeysConfig)()); }); const promiseResult = await Promise.all(promiseAllArr); promiseAllArrKeys.forEach((key: KeysType, i: number) => { cacheGrays[key] = promiseResult[i]; }); return cacheGrays;};
以上是产品能力培植过程中的前端紧张实现及策略,还有很多细节功能比如卡片自动定位到当前韶光最近的事宜卡片项、同步推送协议以及列表无感刷新等功能这里就不再赘述,感兴趣的同学可以联系我。
三、前端技能视角性能体验优化及稳定性培植
性能体验是前端绕不开的话题,我坚持认为这件事不是等项目做完再做优化,而是在业务迭代过程中将它落地。
3.1 性能培植
就协作小程序的完全启动链路图如下,我们在哪些关键节点能做哪些事呢?
协作小程序启动链路图
常识趣能问题规避白屏韶光太长
首屏出来前永劫光白屏,用户体感较差。
大量原生API调用:
通过IDE AppLog面板,创造存在大量的原生JSAPI调用,实在很多是非首屏依赖的,而且JSAPI调用的性能损耗在不同机型下表现不一,尤其对低端机影响较大。
包体积过大:
包体积过大,一方面花费JS初始化实行韶光,另一方面还可能会存在不必要的原生API调用,更加拖累首屏性能。
协作移动端性能体验培植大图以下内容全程高能,往后前端项目就这么做
协作性能培植大图
策略:联合客户端容器、小程序本身履行用户视角端到真个性能策略和稳定性培植案例1:首屏渲染体验优化-优化策略包和效果优化静态资源类似常规的web玩法,紧张便是图片降落包体积
降落包体积不但可以减少js高下文的初始化耗时,还能减少冗余的API调用。
渲染事理大量的setData是拖累性能的紧张缘故原由之一,空想情形该当把从小程序启动到首屏渲染完毕之间的setData掌握在1次。要做到这一点会有一些寻衅,减少不合理的模块re-render,减少setData的数据内容,比如协作单张卡片操作后局部更新,而不是更新全体列表。
考虑动态插件对付非首屏的、功能独立&繁芜、又无法拆到分包的模块,可以考虑将逻辑拆到小程序插件里,按需加载。
案例2:首屏渲染体验优化 - 钉钉客户端lwp预取接入谋定而后动协作没有定位做事等的前置依赖,以是可以前置要求,特殊适宜接入lwp预取。
绝大部分小程序的启动流程,都会经历如下环节:
整体过程是串行的,个中要求首页数据每每和用户的网络环境,做事真个性能有较大关系。串行的流程并行化是一个很常见的性能优化思路,要求首页数据这一步,每每是传一些参数给做事端,获取到数据用来渲染 UI。我们可以抽象出一些规则,在业务的 JS 开始实行前,并行加载首页数据。
根据历史线上数据,小程序的容器启动到业务 JS 开始实行,一样平常须要 1s 旁边,而钉钉的 LWP 要求,均匀实行韶光在 300ms 旁边。这意味着在容器启动阶段,就可以实行 3 次 LWP 要求,这段韶光是被白白摧残浪费蹂躏了。
钉钉lwp预取 客户端、做事端、前端全链路方案业务接入
通过 getBackgroundFetchData 开启客户端通信通道按需读取数据:
dd.getBackgroundFetchData({ type: "lwp", wait: true, success(res) { console.log(res.fetchedData); // 缓存数据 console.log(res.timeStamp); // 客户端拿到缓存数据的韶光戳 console.log(res.path); // 页面路径 console.log(res.query); // query 参数 console.log(res.scene); // 场景值 }});//调用时可以传入 wait 参数,表示是否等待预加载结果。如果为 true,会等待预加载任务完成后收到回调。
结果数据:
有提前注入lwp
没有提前注入lwp
安卓
10ms
299ms
IOS
17ms
30ms
可以看到 iOS 设备由于本身性能较好,减少的韶光有限,但是在 Android 上的提升效果很明显。
说了这么多看一下钉钉App杀进程的冷启动效果,协作全体小程序被容器挟制,也便是常驻内存的保活。
before:骨架屏lwp要求loading after:lwp预取首屏直出
iphone xR 苹果中端机型
接入预取后,除了小程序容器UC内核启动的韶光外,基本上是做到首屏直出了。
3.2 稳定性培植
案例:钉钉lwpcache 无网弱网体验升级协作作为一个大流量入口据数据显示每天有3k+的弱网用户访问量,接入离线方案是改进用户体验的必取之路。
缓存策略:首屏数据直出,后续迭代,将缓存获取机遇从首页onload提前到App onLaunch,能够减少数十到上百毫秒的间隔,是比较常见的手段,但是对降落业务耗时最为直接有效,另一方面从体感上来说确实会更快。
钉钉lwp-cache客户端缓存方案链路流程图
框架容器层面也有小程序快照方案-协作设置页接入,不过也并非所有场景都适用先展示缓存。
离线策略:
1.利用客户端lwp cache能力合营LocalStroage进行上一次要求的缓存数据的离线获取。
2.在前端页面将依赖网络要求的资源图片和功能进行降级。
优化前后比拟
关于稳定性,协作前端还做了全功能的降级体验,如做事端接口非常的重试页、数据为空的降级展示、jsapi失落败后的重试机制等全方位保障系统的高可用性和稳定性。
四、前端视角下的业务思考
做业务要为终极结果卖力,而前端角色从技能/产品思维到业务思维的一跃有很多天然的瓶颈与鸿沟,技能人应该发挥对业务前瞻性的理解,好的架构设计背后一定是对付业务的高度认知与抽象,过程中要对业务关键指标有精确的理解,而不是大略纯功能的堆砌。
1.过度的产品化执念导致随意马虎陷入细节;
2.短缺与业务方永劫光高频度的互动,对商业模式的理解、数据的敏感度不敷;
从产品/技能思维到业务思维的转变,可以考试测验从以下几个方面来培养:
1.培养对目标与数字的敏感度,考试测验网络并形本钱身的订阅报表,定期 Review,多追问指标升降背后的缘故原由;
2.加强与业务方互动,多从业务目标视角看待每个需求,利用 STAR 法则梳理关联关系,多问几个为什么;
3.考试测验结合节制的信息去做公式拆解与沙盘推演,例如 App DAU = (MAU 月均访问频次)/30 +日均拉新,目前的现状每个指标分别在什么量级,每个需求又分别做事于哪个指标,能够提升多少,提升后是否能推导出目标达成,拆解事变并梳理优先级;
4.抛下过于超前的产品执念,避免陷入细节,以产出最小可行产品(MVP)为原则快速试错与迭代,区分好「锦上添花」与「雪中送碳」。
5. 结束语用户越来越深入地利用钉钉,产生了越来越多的协同“事宜”。
「协作」通过高效的推举和筛选机制,帮助用户更好地管理、更轻松地处理逐日纷繁的“事宜”。
「协作」有机会成为用户处理“事宜”的第一入口,让用户更轻松地完成事情。目前正在积累根本能力,已经有二十多个协同场景接入「协作」,逐日用户通过协作处理的任务数靠近 200 万个。
长期代价是,入口更便捷、场景更丰富、推举更精准、操作更大略。