写点什么

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:002910
用户头像

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

关注

评论

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

SpringBoot 源码 | applicationContext.refresh() 方法解析

六月的雨在InfoQ

springboot 源码阅读 Refresh 9月月更 SpringBoot启动流程

高频面试题:谈谈你对 Spring Boot 自动装配机制的理解

Java快了!

Spring Boot

[MyBatisPlus]DQL编程控制②(查询投影、查询条件)

十八岁讨厌编程

Java 后端开发 9月月更

字节跳动基于ClickHouse优化实践之“高可用”

字节跳动数据平台

数据库 大数据 Clickhouse 数据开发 数据计算

恭喜 SelectDB 三位开发者成为 Apache Doris 新晋 Committer!

SelectDB

大数据 开源项目 Committer SelectDB 企业号九月金秋榜

led显示屏有污垢时该怎么清洗?

Dylan

LED显示屏 led显示屏厂家

新一代开源时序数据库TDengine有哪些优势?

TDengine

数据库 tdengine 开源 企业号九月金秋榜

解析Stream foreach源码

华为云开发者联盟

Java 开发 企业号九月金秋榜

Redis 的PSYNC命令

急需上岸的小谢

9月月更

虚实交互,重磅开启|共建多元、互联的元宇宙产业生态圈,赋能上海打造产业高地

Geek_2d6073

前端二面面试题(附答案)

helloworld1024fd

JavaScript 前端

Python中的super函数,你熟吗

华为云开发者联盟

Python 开发 企业号九月金秋榜

Istio Ambient Mesh 介绍

Se7en

PSYNC 部分重同步的原理

急需上岸的小谢

9月月更

网络安全周是什么意思?为什么要开展网络安全周?

行云管家

网络安全 网络安全周

阿里云EMAS移动测试|快速掌握移动端兼容性测试技巧

移动研发平台EMAS

阿里云 应用开发 兼容性测试 移动测试

[SpringBoot系列]基础过渡与夯实(基础配置)

十八岁讨厌编程

Java 后端开发 9月月更

毫末智行董事长张凯:渐进式路线将在智能驾驶竞赛中赢得终局

科技大数据

EMR重磅发布智能运维诊断系统(EMR Doctor)——开源大数据平台运维利器

阿里云大数据AI技术

大数据 运维 企业号九月金秋榜 EMR

了解布隆过滤器

自然

Java core 9月月更

[Spring boot] Spring boot 整合RabbitMQ实现通过RabbitMQ进行项目的连接

Java快了!

Spring Boot

开奖啦!看看8月月更获奖名单有没有你?

InfoQ写作社区官方

热门活动 8月月更

大学三年狂拿国内外十几个3D挑战赛大奖?!国内CG新星崛起

Renderbus瑞云渲染农场

CG 云渲染 3D动画 渲染农场 Renderbus瑞云渲染

开源指南|如何从零开始参与 Apache 顶级开源项目?(二)

SelectDB

数据库 大数据 Doris 开源治理 企业号九月金秋榜

怎么合理使用索引

急需上岸的小谢

9月月更

【云原生】Kubernetes操作精讲

陈橘又青

9月月更

你必须知道的Java泛型

自然

Java core 9月月更

开源云管平台有哪些?有哪些优势?

行云管家

云计算 云平台 云管平台 云管理

[MyBatisPlus]DQL编程控制①(条件查询)

十八岁讨厌编程

Java 后端开发 9月月更

容器化|自建 MySQL 集群迁移到 Kubernetes

RadonDB

MySQL 数据库 RadonDB Kubernetes 集群

不懂 Kubernetes 实现云原生是什么体验?

北京好雨科技有限公司

云原生 #Kubernetes#

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