前端早早聊大会-与掘金联合举办-全年学习
钉钉同构表技术应用-叶斋_00.jpg

各位线上的同学们,大家好。我是来自钉钉文档团队的叶斋。感谢前端早早聊的举办方,让我有机会在这里和大家分享。我的题目是《钉钉同构表技术应用》。

关于讲师

先简单地做个自我介绍吧。我叫谢光磊,花名叶斋。毕业于南京大学地理系,硕士在中科院遥感所。我是在 14 年校招进入淘宝前端,19 年的时候转到钉钉文档团队,目前负责钉钉文档表格类产品的研发。熟悉我的人,都说我身上有一个标签那就是 WebGL,其实我对 WebGL 很感兴趣,曾经翻译过《WebGL 编程指南》这本书,然后也是 WebGL 3D 渲染引擎 G3D 的作者。我的邮箱,和我的博客都在下面,这次分享之后,如果大家想再与我进行更进一步的交流,欢迎给我发邮件。

钉钉同构表技术应用-叶斋_01.jpg

一、释题

钉钉同构表技术应用-叶斋_02.jpg

接下来我就开门见山了。题目中的同构表,是什么意思呢。其实所谓同构表,是指表中的数据遵循相同的结构

一提起表格,大家想到的应该都是 Excel 这样的产品。但是,Excel 这样的产品,其出发点是什么呢,是排版。因为 Office 最初的出发点,就是为了去替代打字机,去把发生在纸张上的办公行为电子化,不管是文字产品 Word,还是表格产品 Excel,都是这样。所以我们会发现,诸如类似于合并单元格,字体字号调整,左对齐右对齐垂直居中,这些排版功能在几乎最早的 Excel 中就已经存在了。

在排版表格中,数据其实是以单元格的形式去组织,没有很严格的行列概念,或者说,同一列中可能会出现不同类型的值,比如上面一个单元格内是文本,而下面一个单元格是数字。这是,表格内的数据其实并不遵循相同的结构,我们又称 Excel 这种排版表格为异构表。

相比而言,同构表的出发点是数据同构表的数据必须是按照行列严格组织的,每一行数据的结构完全一致;同构表中基本不会出现诸如合并单元格这样的操作,同构表最关心的是对同构数据的操作和分析:增、删、改、查;筛选、排序、分组、聚合等等等等。

当然,我这里并没有说 Excel 就不具备这些数据分析能力。其实 Excel 作为目前使用最广泛的表格类产品,有着非常强大的数据分析能力,不仅有排序、归并、分类、公式计算,还有数据透视表这种杀手级的 feature。我只是说 Excel 的出发点是排版表格;其实经过了这么多年的演进,它已经是当下桌面软件中规模最大,功能最全的数据分析工具了。我们团队内部也在做对标 Excel 的异构表类的产品,只不过我今天分享的是一款更轻量,更易用的同构表。

二、钉钉同构表一瞥

钉钉同构表技术应用-叶斋_03.jpg

先看一下同构表长什么样。在场的大部分都是前端,如果你用过 AntD 的话,你会发现同构表其实和 AntD 的 Table 组件有一点点像。但是 AntD 的 Table 其实只是简单的展示,而钉钉同构表的功能则强大得多。我这里录了一个简单的视频,帮助大家了解一下我们做的同构表究竟有哪些功能。

可以在视频中看到演示的一个效果,大概五分钟左右。

钉钉同构表技术应用-叶斋_04.jpg

钉钉同构表技术应用-叶斋_05.jpg

这里我列出了同构表支持的完整的功能集合。其实最重要的是数据编辑数据分析两块功能。其中可能一半多的功能,在之前的视频里都展示过了。

数据编辑操作其实都是行列操作数据分析操作包括排序,筛选等等。

因为这个组件确实比较复杂,在短短的一个小时内,确实也很难成体系的把这里面的技术实现讲清楚。所以,接下来,我想着眼于一些局部的技术细节,来向大家介绍同构表,以及通过这些细节,讲讲我们在「前端搞文档」这个很细分的领域,我们是怎么思考的,我们在做同构表的过程中,在技术上是如何作决策的,我们看问题的角度是什么。

三、大数据场景下的性能保障

钉钉同构表技术应用-叶斋_06.jpg

钉钉同构表技术应用-叶斋_08.jpg

计算性能和渲染性能。同构表支持了 10 万行这个级别的数据量。在这个数据量级下,同构表只有在初始化加载数据的过程中需要等待一到两秒,中间的各种操作是非常流畅的,这是怎么做到的呢?

虚拟滚动

同构表是基于 DOM 来渲染表格内容的,10 万行数据,百万级别的单元格,首先我们不可能在页面中生成百万级别个 div 元素 …… 那绝对会使浏览器原地爆炸。理想情况下,我们在表格中滚动时,只应该渲染能够看到的单元格;这就是虚拟滚动。

所谓虚拟滚动,就是不在使用浏览器原生的滚动,而是通过监听元素的滚动事件,在 JS 中维护滚动的偏移量,并且把滚动的内容,也就是单元格手动地放到滚动后的位置上。这样,虽然滚动的过程中我们会不断销毁和重建元素,但是至少同一时刻,只会有不超过 200 个单元格真实地在页面里——这就容易接受地多了。

虽然听上去不是很难,但是这里细节还是挺多的,比如不同设备滚动事件触发的频率和速度要去归一,比如在移动端滚动到边缘有回弹效果等等,这样滚动的体验才会和非虚拟滚动的体验一致,否则会滚动得很死板,感觉很不一样。

还有个有意思的细节,就是当我要把单元格放到滚动后的位置上时,我需要知道单元格滚动前的位置,也就是单元格左侧所有单元格的宽度累加,和单元格顶部所有单元格的高度累加。而单元格的宽度其实是不同的。去取这份数据的频率其实很频繁,而累加的时间开销也比较高,所以我们内部还有一个专门的对象去做这件事,用二叉树结构,把累加的时间复杂度从线性级别降低到对数级别。

钉钉同构表技术应用-叶斋_07.jpg

我其实挺喜欢一句话的,就是「屏幕上只有这么多像素」。今天,JS 能够轻易 handle 的数据的量级,早已远远超出了一块屏幕的像素数量。所以前端页面的渲染问题,不管是 DOM 渲染还是其他的什么,你的页面再庞大,内容再多,交互再复杂,在这句话面前都没有太大的辩驳的余地。

数据切片

虽然同构表把不必要的渲染藏起来了,但计算却是实实在在发生的。在 10 万这个量级下,如果我们改了一个单元格的值,而这个单元格恰好影响着排序,那就得重新排序;如果恰好在统计这个单元格所在的列的最大值或中位数,那这个值也需要重新计算。这些计算的开销都不低,而且不可避免,这时怎么办呢?

解决的方案很粗暴:我们把整个 Model 层放到了 Worker 里面跑,让它自己在里面算,卡不到外面就好了。这时肯定有同学会问,这样 Worker 内外通讯的成本,10 万条数据,每有一点改动,都要传一下,岂不会很高?

这里就要引入我们的另一个优化点了,就是数据切片。在 Worker 外面,我们根本不去拿全量的数据,我们只拿那些出现在屏幕中的数据。当表格滚动时,组件层向模型层发送消息,我需要第几行到第几行,第几列到第几列,一个 Range;然后模型层把这个 Range 的数据切出来,发送给组件层。

钉钉同构表技术应用-叶斋_09.jpg

从 Worker 这件事,我开始意识到,当我开始做复杂的前端应用时,我其实就已经是在开发桌面应用或 native 应用了。这和传统意义上的前端做页面完全不一样,桌面应用面临的场景,遇到的问题,比如如何保证断网可用,如何保证用户长时间使用不 crash,这些前端页面不太需要关心的问题,在做文档级前端应用时,一个都不会少。

钉钉同构表技术应用-叶斋_10.jpg

但是我们的工具也多,桌面应用有多线程,我们有 Worker,桌面应用有 QT / WinForm,我们有 DOM,桌面应用有 GDI / Skia,我们有 Canvas,桌面有 Socket,我们有 WebSocket,桌面有 FileSystem,我们有 localStorage …… 不胜枚举。对每个功能,其实浏览器已经给你准备好了一个弱化版的代替品,弱归弱,但是能用啊。

钉钉同构表技术应用-叶斋_11.jpg

说到这里我需要提一下 TypeScript。开发桌面应用或 native app 的语言,从 C++ 到 C# 到 OC / Swift 到 Java 甚至 Flutter 的 Dart,从来都是强类型语言(也许是动态语言,但一定是强类型语言)。但是前端开发手里只有 JavaScript,直到 TypeScript 横空出世。有一点我非常确定,就是对复杂的桌面级前端应用来说,TypeScript 在未来三年内,几乎唯一的选择。

四、无鼠标操作面临的问题

钉钉同构表技术应用-叶斋_12.jpg

上面讲了大数据量的问题,接下来我想从另一个细节来讲讲同构表。不知道大家有没有注意到,表格这种文档类的前端产品,对键盘的响应其实是很敏感的。在几乎所有表格产品中,当你选中一个单元格时,你可以通过方向键去移动选中单元格,如果选中的单元格移动到了滚动视窗之外,表格会随之滚动;更重要的是,如果你这是开始在键盘上进行输入,注意这时虽然你选中了单元格,但它并不是 focus 状态,也就是没有正在闪动的光标,这时你在键盘进行文字输入,都会直接进入 focus 状态进行编辑。

这部分其实是我们团队另外一个同学完成的,当我看到代码的时候,我才发现,我之前确实存在很多知识盲点。比如,不知道有多少同学知道 **onCompositionStart****onCompositionEnd** 方法,知道 **document.createRange** 是干什么用的。当我们通过输入法输入非英文,比如中文的时候,其实有一个中间状态,你看到英文字符在 input 框里面,等你按了回车,你选中的中文字符才会正式填充进去。这个过程,其实 JS 也是可以干预进去的,只不过在开发传统前端页面时,99% 的时刻你是用不到这些 API 的。一个 input 框,你在里面进行字符输出,你可能只需要用到一个 onChange 事件,对吧。但是当你遇到复杂度很高的应用时,你就需要干预进去,去控制光标的位置,去控制选区的位置。

我再举一个例子,就是在无线设备上,键盘其实是一个很棘手的事情。因为你必须依赖页面上的某个,比如说 input 元素来唤起。所以其实很多表格类产品在移动端的表现都是,你选中了单元格之后,底部出现一个编辑框,你得在那个编辑框里面输入内容(当然这是个产品问题)。而键盘一旦唤起后,占据了屏幕上的一部分空间,其实会导致页面发生了一些变化。有多少同学去研究过,键盘弹起时页面究竟发生了什么?页面的 clientSize 发生变化了吗,触发了 resize 事件吗?还是说页面没变,只不过外面又套了一层滚动呢。如果这个变化导致触发键盘的 input 元素失去焦点,又会怎样?这里有非常繁杂的问题需要去实验,去解决,去为不同的设备作兼容。对于简单的一张报纸型的页面,或者小票型的页面,浏览器提供的方案就已经能满足要求了,反正弹起来就滚动嘛,但是对于桌面应用来说,滚上去就是了。但对于文档级应用来说,就需要自己处理。

钉钉同构表技术应用-叶斋_13.jpg

说了这么多,是什么意思呢?我想说的是,在研发文档级应用时,我们几乎要重新认识浏览器,那些冷门的知识,在 MDN 上几乎没有任何解释的 API,有可能会突然成了救命稻草。为什么呢,因为这些 API 很可能来源于操作系统本身的一些概念,比如 onCompositionStart,当我真正了解到它的含义时,我很确定操作系统的输入法接口中,肯定有对应的概念存在。

还是回到那点上:做文档级应用就是做桌面应用,当常用的接口满足不了需求的时候,你只能从浏览器中寻找更细粒度的方案。我这里不是说 localStorage 或者 Websocket 这种热门技术,而是注入 onCompositionStart 这种真正隐藏在角落里的东西。所以,重新认识浏览器吧。

钉钉同构表技术应用-叶斋_14.jpg

这里我想多说一句 React。React 现在是最热门的前端开发框架了。但是,React 不是银弹。其实 React 并没有经过文档级复杂度前端应用的考验。在文档这种复杂前端应用中,大量存在各种各样的优化,而这种声明式的编程方式,优化起来要比命令式的 DOM 操作难很多。举个例子,同构表目前还不是受控组件。我们知道 React 中有受控组件和非受控组件的区别,比如一个 input 组件,你传 defaultValue,那就是非受控,你不需要关心组件内部的行为,你只需要在合适的时候去从 ref 上取 value 就行了;如果你传的不是 defaultValue 而是 value,那么就是受控模式,你需要自己在 input 外面去管理 value。

五、不成体系的总结

最后简单地做个总结吧。因为写这个稿子的时候也是想到哪儿写到哪儿,不是很成体系,所以我就把一些我觉得有价值的点单独再拿出来,作一个不成体系的总结吧。就是前端怎么做文档

首先是编程语言

只要还在前端这个领域里面,TypeScript 必不可少

更全面的前端能力

比如遇到密集计算型性能瓶颈的时候后,你能不能想到把部分内容放到 Worker 里面去;比如说要做断网使用优化的时候,能不能想到 Service Worker,能不能想到 indexDB 而不是 localStorage;光想到了还没用,你有没有这个工程的能力,来把它落下来,把它用好,所以这需要前端工程师具备全面的能力。

需要准确、细致的前端能力

之前说一些冷门 API 是一个例子,另一个例子是样式:不知道大家写业务页面的时候,有没有一种「囫囵吞枣」的感觉,反正照着经验就写出来了,如果发现有哪个样式不对劲,就随便改改再试,也不细究为什么。如果在文档级的产品里,还是这么搞的话,很快样式就会变得一团糟。所以更准确、细致的前端能力,就是说,你在研发的过程中,非常清晰地知道每一行代码,为什么要这样写,最好还要清楚背后的机制是什么。

借鉴已有的成熟设计

最后一点总结,就是文档类的产品,作为桌面端应用的历史非常久远,有很多设计,最佳实践,其实都有现成的方案可以抄,我们做的,其实就是把桌面应用在 Web 上重新实现一遍,说迁移肯定是过分了,但可以复用的方案有太多太多了,就比如之前我提过的,那个用二叉树来解决宽度累加时间开销问题,就是我们团队一位来自原来微软 Office 团队的同学带给我们的。其实我们团队有不少来自微软 Office 或者其他文档产品团队的同学。

团队

这样,就自然转进到了下一个环节,就是团队的招人广告。

简单介绍下我们团队:我们是钉钉文档团队,愿景什么的就不多说了。说点实际的,它是阿里巴巴目前投入最大,HC 最多的文档团队,想象空间非常大。钉钉有着亿级的 DAU,团队组建了一年多两年不到的时间,正在快速的增长当中。

目前我们做的事情:

  • 一类是基础产品
    • 钉盘,对标 OneDrive;
    • Office 三件套:文字,表格,演示;
  • 另一类业务透出
    • 日志、知识库;
    • OA 后台,远程会议,在线教育。

全都是自研,在钉钉内部也不光是做基础产品,我们要把这些基础产品的能力透出给业务,前面说想象空间非常大,当然这不只是钉钉的想象空间,也是文档的想象空间。

除了这些,钉钉现在还在做智能硬件,智能会议室,远程会议,音视频,在线教育。所以讲了这么多,如果就是对自己的技术比较有信心,愿意来提高一下天花板,可以来联系我或者联系钉钉团队的其他同学。

钉钉同构表技术应用-叶斋_16.jpg

钉钉前端团队

荐书

钉钉同构表技术应用-叶斋_18.jpg


别忘了点下方送稻谷