阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

Slack 使用 React 重写 Web 客户端

  • 2017-06-13
  • 本文字数:2996 字

    阅读完需:约 10 分钟

Slack 使用 React 重写了 Web 客户端。在这篇文章中,他们以重写 Emoji 选择器为例,展示了 React 在性能和代码可维护性上给他们带来的巨大好处,以及给用户带来的体验升级。查看英文原文: Rebuilding Slack’s Emoji Picker in React

Slack 正在将 Web 客户端迁移到 React。在最开始,我们的前端使用了 jQuery 和 Handlebars。后来,社区开发出更好的方案用于创建可伸缩的、基于数据驱动的用户界面。jQuery 的“渲染后修改”模式直截了当,但无法与底层的模型保持同步。不同的是,React 的“渲染后再渲染”模式可以保证渲染和模型的一致性。Slack 也紧跟业界的步伐,不断改进前端的性能和可靠性。

我们认为,要引入 React,最好的办法就是先用 React 重写现有产品里的某个特性——这样我们就可以比较出新的开发流程和结果与原先的有什么不同。我们需要重写一个组件,这个组件必须具备交互性,能够自包含,并能够体现 React 在性能方面的优势。我们很快就找到了一个绝佳的组件——被重度使用且极度复杂的 Emoji 选择器。

Virtual DOM 的优势

阅读这篇文章要求对 React 有一定的了解,如果你不熟悉 React,建议先阅读一下 React 的官方文档。简单地说,React 是一个JavaScript 代码库,可以用它方便地开发声明式的、基于数据驱动的用户界面。它的API 很简单,主要由一个组件类组成,这个类包含了一些生命周期方法。组件本身不会生成HTML,相反,它们会生成类似DOM 的树,叫作Virtual DOM。React 会比较两个Virtual DOM,并使用最少的操作将其中的一棵树转换成另一棵树。例如,你可以告诉React 基于新的模型数据重新渲染整个视图,它就会以最快的速度帮你更新文本节点,就好像它有一个精灵军团在帮你完成DOM 的更新操作一样。

React 擅长于将组件在单个模板上的各种行为合并在一起。举个例子,假设一个 Slack 频道变为未读状态时,你通过 JavaScript 来更新频道的边栏:

  • 找出这个频道的 ID
  • 在 DOM 里查找这个频道对应的节点
  • 将节点状态切换成未读(应用 CSS 类)

这个过程很简单,不过你还得为其他事件编写不同的处理逻辑,比如“create”、“join”、“leave”和“rename”。相反,React 把这 5 中情况合并在一起统一处理:

  • 使用新的模型数据重新渲染频道边栏。

我们不需要为每一种 DOM 操作编写代码,而是重新渲染整个组件,React 会为我们完成这个过程。React 通过让代码变得更通用(一刀切的模板)来简化开发。

Emoji 选择器

Emoji 是 Slack UI 的一个组成部分,是最理想的 React 组件。它动态、离散,只需要少量的输入——一组 emoji、默认皮肤和用户的 emoji 使用历史。刚好现有的 Emoji 选择器需要进行性能调优,因为现在不管 emoji 会不会出现在视图里都需要进行渲染。在查找 emoji 时需要切换每个 emoji 的可见性,在重度使用时性能很成问题。新的 Slack 团队准备了 1374 个默认 emoji,这还不包括自定义 emoji(在写这篇文章的时候,Slack 团队总共有 3126 个 emoji,有些团队甚至更多)。重写 Emoji 选择器将会对 Slack 的日常使用产生重大影响。

我们选择在 Storybook 里开发新的组件,Storybook 自称是一个“会让你喜欢上它的 UI 开发环境”。它不要求你改变开发方式,但会让开发、测试和代码审查变得更有趣。你可以在 Storybook 里通过指定不同的属性来定义不同版本的组件。我们为 Emoji 选择器增加了一个新皮肤和几种 emoji 查找方式。

组件布局

React Emoji 选择器的根组件是有状态的,而子组件则是无状态的。我们按照惯例把每个组件导出到单独的文件里。结构如下所示:

Header

  • 分类选项卡:列出了 emoji 的类别,每个类别都有一个“jump to”链接。
  • 搜索框:通过 emoji 的名称或别名过滤 emoji。

Body

  • 固定的头部:显示当前类别选项卡的名称。
  • emoji 列表:所有类别的 emoji 虚拟列表。

Footer

  • emoji 预览:当前选择的 emoji 大图预览。
  • 皮肤选择器:显示当前的皮肤,并可以切换到其他皮肤。
  • 快捷动作(可选的):emoji 的子集,用于快速回复消息。

React 为编写无状态组件提供了两种方式:PureComponent 类和 function。function 更为简单一些,不过它们在每次合并时都会进行渲染,会影响性能。React 团队计划对 function 进行优化,不过目前最好还是避免使用它们。于是我们选择了 PureComponent,它预定以了 shouldComponentUpdate 方法,这个方法可以防止在遇到相同属性时进行更新操作。

React 是一个视图层,把它与自己开发的应用集成要比把它与标准的框架集成直截了当得多。我们不应该破坏 Emoji 选择器的封装性,这样才能很好地与 Slack 现有的模式集成在一起——我们希望这个组件就像是从一个端到端的 React 应用里拿出来的一样。为了保持选择器的纯净,我们在现有的模块系统里创建了一个轻量级的适配器。适配器挂载选择器组件,抽取模型数据,并监听来自外部的信号。采用这种模式,我们可以在开发新功能的同时逐步地迁移代码库。

新的开发流程

虽然使用 React 进行开发是一件很愉悦的事情,但将它集成到我们已有的开发流程里却不是那么一回事——至少在一开始不是那么令人愉快。在那个时候,Slack 使用的是自己开发的前端构建管道,没有所谓的导入、依赖或者复杂的转换(比如 transpilation)。我们决定采用 JSX 语法和 ES2015+,并使用 Babel 和 webpack 在本地构建 Emoji 选择器的资源。

我们预期签入本地编译的代码会很痛苦,但我们低估了接连发生的合并冲突和依赖管理问题是多么令人抓狂。最后,我们尝试将 webpack 集成到我们的开发和 staging 环境里,目标是无缝地替代已有的工作流。为此,我们做了如下的工作。

  • 基于 webpack-dev-server 开发了一个服务,当相关资源和依赖发生变更时,自动编译本地开发服务器上的资源。
  • 支持将 webpack 资源加载到单元测试里(这样就有可能为 React 组件编写测试用例)。
  • 重构生产环境的构建流程,将 webpack 资源推送到我们的 CDN。

通过重写 Emoji 选择器,迫使我们反思我们的构建管道如何能够以一种更健壮、更具伸缩性的方式打包资源。

性能

我们在少量的团队里部署了新的组件,并观察结果。我们观察了 Emoji 选择器在用户使用不同的 5 种交互方式下的渲染速度,对于大部分的操作,React 表现出了显著的速度提升。以下列出了选择器在正常规模团队里的不同渲染时间。

  • 第一次挂载:-270 毫秒(减少了 85%)
  • 第二次挂载:-158 毫秒(减少了 91.3%)
  • 搜索(多个结果):+27 毫秒(增加了 259%)
  • 搜索(一个结果):-25 毫秒(减少了 53.2%)
  • 重置搜索:-68 毫秒(减少了 70.1%)

最大的改进来自“第一次挂载”,从 318 毫秒到 48 毫秒,减少了 270 毫秒,也就是 85%。这要极力归功于 react-virtualized ——一个虚拟列表代码库——减少重新渲染 emoji 的数量。在默认视图上,React Emoji 选择器比 DOM 少渲染了 85%。

或许最让人感到吃惊的变化来自“搜索(多个结果)”,时间从 17 毫秒增加到了 44 毫秒,增加了 27 毫秒。旧选择器只是把不匹配的 emoji 隐藏起来,也就是说,当匹配到大部分 emoji 时会相对较快。但它的缺点也是显而易见的,“搜索(一个结果)”和“重置搜索”就让它的缺点原形毕露,因为此时它需要隐藏更多的 emoji。

未来

使用 React 重写 Emoji 选择器加快了渲染速度,同时简化了代码,让代码更容易维护。我们正在使用 React 重写剩余的代码。我们还有很多工作要做,这次重写将为用户的日常体验带来积极的影响,为此我们感到非常兴奋。与此同时,我们积累了 React 的实践经验,可以帮助平台更进一步。

2017-06-13 19:002794
用户头像

发布了 322 篇内容, 共 134.2 次阅读, 收获喜欢 144 次。

关注

评论

发布
暂无评论
发现更多内容

实时视频在弱网下的极限通信

Damon

7月月更

必须掌握的CSS三大特性🎨

猪痞恶霸

前端 7月月更

Istio Mesh模型

阿泽🧸

7月月更 Istio Mesh

微信业务架构图&“学生管理系统”毕设架构设计

gump

架构实战营

Java方法重载及递归

未见花闻

7月月更

阿里云架构师马颂:云上高性能计算助力基因测序

阿里云弹性计算

高性能计算 EHPC 基因测序

解读《深入理解计算机系统(CSAPP)》第10章系统级IO

小明Java问道之路

unix 文件管理 csapp 7月月更 系统级IO

Docker 安装 MySQL8.0

宁在春

MySQL Docker 镜像 7月月更

【刷题记录】10. 正则表达式匹配

WangNing

7月月更

Typora常用语法和md样式美化一本通

武师叔

7月月更

实习过后的人都怎么样了?

KEY.L

7月月更

“穿越”到虚拟世界笑风生,网易瑶台沉浸式活动平台创新云端活动体验

阿里云弹性计算

虚拟世界 GPU服务器 瑶台

Python爬虫抢购某宝秒杀商品

弑着去忘记う

Python

IPv6大航海,风帆指向强应用

脑极体

容器应用发布三大方案

穿过生命散发芬芳

容器应用 7月月更

实践丨手把手教你用STM32设计WiFi语音播报日程表

华为云开发者联盟

开发

「势说新语」浅谈软件许可证

安势信息

开源 软件 许可证 开源软件 开源软件供应链

【Docker 那些事儿】容器数据卷的本手

Albert Edison

Docker Kubernetes 容器 云原生 7月月更

AWS Inspector

冯亮

云计算 DevOps security AWS

小程序表单-3

小恺

7月月更

数据建模

奔向架构师

数据仓库 数据建模 7月月更

赛博女娲,怎么造数字人?

脑极体

zookeeper-zookeeper常用命令

zarmnosaj

7月月更

leetcode 435. Non-overlapping Intervals 无重叠区间(中等)

okokabcd

LeetCode 数据结构与算法 贪心算法

KubeEdge Summit 2022首日亮点 | 全球产学研齐聚一堂,共话边缘新未来

华为云原生团队

云原生 边缘计算 kubeedge 边缘AI IOT设备管理

新星计划Day7【数据结构与算法】 栈Part1

京与旧铺

7月月更

浅入浅出mybatis(四)

ES_her0

7月月更

Flutter 来一个笑嘻嘻的动态表情

岛上码农

flutter ios 前端 安卓开发 7月月更

长安链研究笔记-数据存储

长安链

SENSORO智慧社区服务方案:抓住基层治理的“神经末梢”

SENSORO

物联网

LaTex笔记(Windowns)

乌龟哥哥

7月月更

Slack使用React重写Web客户端_语言 & 开发_Chris Montrois_InfoQ精选文章