大规模应用TypeScript「2019 JSConf -Brie Bunge」

特别说明

这是一个由simviso团队进行的关于Airbnb大规模应用TypeScript分享的翻译文档,分享者是Airbnb的高级前端开发Brie Bungejavascript

视频翻译文字版权归simviso全部,未经受权,请勿转载前端

图片描述
参与翻译人员名单
netty.pptx.pngjava


1. 介绍

图片描述
你们好,个人名字是 Bree,我在Airbnb工做。众所周知,在大公司中进行大的改革很难。这须要去说服不少人,同时又须要涉及大量的代码迁移。我想要与你们分享咱们是如何将 TypeScript 应用到 Airbnb 这个公司的平常开发中的。react

我很感谢大家能在这里,我知道大家彻底能够披着时髦的毛巾在海边娱乐。git

我设想这里的每一个人都会有这样一些问题,你要为你的公司进行重大的变革,同时这可能会被做为一个案例进行研究。你如今是否在公司内部积极的推进将项目开发迁移到 TypeScript,为此我将提供一些技术和工具上的帮助。若是你以前已经听过并但愿对 TypeScript 了解更多。
图片描述
首先,咱们会介绍 TypeScript 是什么?规模化又意味着什么?对于将TypeScript规模化的过程又有什么建议?基于这些问题和疑问,我会给出相应的解答。咱们该经过什么样的迁移策略将 JavaScript 逐步迁移到 TypeScript?程序员

1.1 什么是TypeScript

图片描述
请你们快速举手示意一下以方便我知道大概有多少人以前使用过 TypeScript。github

cool,有这么多人!npm

还有一部分人没有举手。那么,我会给出一个快速介绍,这样每一个人都在同一块儿跑线上了。
图片描述
假如咱们有这么一个 greeter 方法。它接收一个 name 参数,而后返回 hello + name。那么,若是咱们传入的是JSConf,它会和和睦的说 Hello,JSConf!
图片描述编程

将刚才的代码用 TypeScript 来表达就是这个样子,能够看到他们很像。惟一的区别是咱们在它的参数这里使用了类型注释。因此若是咱们在咱们的 TypeScript 项目中使用这个函数同时咱们传入一个字符串,能够看到编译一切正常。
图片描述
可是在这种状况下,若是咱们传递的参数类型不是字符串,而是一个字符串数组。那么,如图所示TypeScript 就会给咱们一个错误。即这是一个字符串数组,函数接收的参数类型是 string,不支持该分配。咱们不须要再经过点击刷新页面这一流程来从咱们的控制台中查看错误并肯定该错误所发生的位置。能够看到,在咱们输入后,当即就从编辑器中获得了这个错误。
图片描述
咱们也能够表达其余对象类型。这个接口描述了一个包含名字和姓氏的 person 对象。同时,你能够定义更复杂的构造类型。
图片描述
TypeScript一般带有一个编译器。当出现问题时就能够立马告诉你。它还有一个能够与编辑器挂钩的语言服务器,能够帮咱们进行自动编译,提供重构相关提示等等。在这个例子中,咱们已经在这个组件中导入了withStyles react。这样就能够自动导入它所须要的数百个CSS属性,包括内联文档。神奇吧!我不必来回浏览文档页面,我在编辑器中就能够获得这些全部。经过在咱们的代码中使用类型,咱们能够作更多的事情。咱们只是稍微接触了下TypeScript的类型限制,可是能够经过这个来帮你捕获这种类型的错误,以及支持它的工具。segmentfault

2. 什么是规模化

图片描述
关于TypeScript的内容就到此,那关于规模化应用这一部分呢?恩?这有个什么问题?感谢TypeScript帮我找出了这个错误。若是你一时疏忽,输入了一个错误的变量,那你将会获得这样的一个错误提示。那么,在 TypeScript 中真的会发生这样的事。因此,让咱们来修复下。

规模化会改变咱们的交流方式。我之前在小团队的时候,若是你想要使用TypeScript。是的,听起来很酷。那咱们就用。但当团队规模达到数百位工程师,同时代码量也愈来愈多,那么交流方式就要发生改变。

咱们须要进行一些改变,即当咱们想要在咱们的主仓库中使用TypeScript的时候(这里指Powers | airbnb.com的主仓库)。同时让TypeScript成为前端主要开发语言。这个改变影响的人越多,那么必须迁移的代码也就越多。

那咱们用数字来讲明规模化意味着什么。
图片描述
airbnb(我所在的公司)拥有大量的JS代码。在咱们的主仓库中有两百万行以上的JS代码,以及100多个内部npm包。咱们有几个分离的仓库,经过打包到内部的NPM注册中心,这样就能够跨仓库共享。这真的有不少代码,我甚至能看到一些 Backbone 的代码,能够想一下JavaScript走过了多少年,它在Airbnb这里也走过了十多年。
图片描述
因此咱们有大量的开发人员在维护这些代码。目前公司有超过1300名开发人员,其中有200个以上是前端。这些前端工程师大多数都参与了主仓库的贡献。这些数字给咱们展示了当时咱们提议要使用TypeScript时所面临的大环境。

3. 使用TypeScript的提案

图片描述
那么在这种规模下,咱们当时是一个什么样子呢?

每月,咱们都会将全部的前端工程师聚在一块儿开个比较有意思的会,一块儿讨论新的前端技术和选型。为了能够作到深思远虑,咱们起草了提案。它对某个新技术进行了诸如优势、权衡、替代方案、针对退出方案的思考以及长期拥有者等方面概述。你们会权衡这些提议的利弊。咱们会站在团队的角度去决定向前迈出这步是否有意义,这确保了咱们能够做为一个集思广益的团队,对所作的事情作出深思熟虑的决定。这样能够避免在没有正当技术理由的状况下就“上车”。

从2016年起,Airbnb 已经在一些小规模团队中探索使用 TypeScript。在2017年的前端调查中,静态类型系统呼声最高。基于这个信号,Joe(第二排那个Joe)和我起草了一个关于TypeScript的提案,并将它交给前端工做组。这项提案详细说明了为何在Airbnb使用TypeScript是有意义的。

让我来说讲主要的缘由。airbnb的使命是要让每个人都感觉到世界到处都是家(airbnb是一家旅游住宿的公司)。用户对咱们的产品提出的每个建议都能会让咱们向着这个目标更进一步。这对于你正在开发的产品也是如此。
图片描述

  1. TypeScript能够帮助咱们阻止bug的发生。
  2. TypeScript还为开发人员提供大量的生产力效益和工具,像咱们以前看到,自动编译和重构。使用TypeScript,工程师能够更安全快速的迁移代码。
  3. 在Airbnb咱们引入了GraphQL 和Apollo,它可使咱们经过自定义的GraphQL模板来生成TypeScript类型

这意味着咱们能够获得端到端之间的类型安全。由于前端和后端所使用的数据类型共享了同一个事实上的定义源。后端工程师可以在不影响客户端的状况下对API进行修改,而前端工程师则能够确信哪些数据将从服务器返回。类型不匹配一直是咱们的主要bug所在。因此,这种端到端的类型安全性是一个主要的卖点。

4. 如何解决问题

图片描述
这听起来很棒! 但对于咱们的初步提案,还有不少问题和疑虑。让咱们来对TypeScript进行更深刻的了解。
图片描述
咱们的主仓库依赖了一些咱们内部的NPM包。为了得到自动完成和类型检查的能力,咱们须要先将它们转换成TypeScript,这样作是否值得?
图片描述
这也是咱们目前面临的困境,咱们的TypeScript 项目依赖于一个JS NPM包。那咱们该如何获取这个包的类型?
图片描述
这看起来像是须要首先将这个包转换为TypeScript。但这里有个问题,由于维护人员不容许咱们对它作TS转换操做,可能他们也不情愿这么作。由于在咱们提案的早期,咱们并不肯定是否要一直按照这个提案走下去。可是从另外一个层面来说,咱们使用TypeScript是为了可让开发人员能够有更好的体验。咱们须要TypeScript提供的类型安全性。那么咱们该如何解决看似鸡和蛋的问题呢?
图片描述
TypeScript有一个叫作声明文件的功能。即一个以.d.ts为扩展名的文件,经过它咱们能够为JavaScript文件定义类型。

让咱们来看一个例子。
图片描述
如图所示,一块儿来看咱们以前看到的greeter方法。它上面是对应的.d.ts文件,方法里没有实现细节,它只描述了类型。TypeScript将它们组合到一块儿,这样,在编译是使用这个声明文件,在运行时使用这个原生的JS文件。
图片描述
那么咱们回到咱们刚才提的问题(要不要一开始就转换),看看声明文件是如何提供帮助的。

固然,若是那个项目已转换为 TypeScript。咱们就没有必要再生成一个.d.ts文件来做为TypeScript构建时的一部分(由于在使用TS编程的时候,要经过它对原生JS进行调用)。但咱们认为这不止一种选择。相反,咱们能够将声明文件放在咱们的 TypeScript 项目中。
图片描述
图片描述
另外一个选择则是咱们能够建立一个单独的NPM包,并将声明全部声明文件放入其中。这很棒,由于如今能够如今跨多个仓库共享声明这些文件。经过这些你能够在使用相似React时,进行相应的类型检查。在安装React的同时你能够安装@types/react包。
图片描述
在这个@types/react包中针对React的5000个经常使用包作了类型声明。@types/react与其余5000个其余包都在DefinitelyTyped 这个仓库中,它由社区在维护。在咱们的主仓库中,绝大多数的公共依赖都已经由DefinitelyTyped 作到了类型声明。有活跃的社区氛围是TypeScript 的一个主要卖点。咱们也回馈了一点力量,相信在这个房间里也有人作出了贡献。谢谢。

这些公共的NPM包的类型声明已经有DefinitelyTyped 在作了,但那些内部的包该怎么办?咱们本身安装了一个DefinitelyTyped镜像,在它经过建立一个单独的NPM域(@airbnb-types/*)。这样,你只须要安装@airbnb-types便可。这个仓库的设置与DefinitelyTyped相似,因此咱们能够在里面添加并发布这些内部类型。
图片描述
咱们开源了一个starter 工具包,若是你有兴趣的话,能够来参与下。它里面没有类型定义,它只是在教你如何进行一些配置以便于进行测试或者发布本身的类型定义。
图片描述
那么 TypeScript 究竟能帮忙避免多少 bugs 呢?近期,一个叫作“该不应作类型定义”的研究代表,在选择了TypeScript 的 GitHub 仓库中,有 15% 的 bugs 获得了避免。
图片描述
在咱们内部,有一个记录生产环境事故的流程。这个流程的本意并非为了责怪谁,而是要从错误中进行学习,这样咱们以后就不会再犯相似的错误。因此我坐下来读了六个月的总结报告,阅读这些总结报告颇有意思。我最喜欢的就是未捕获的异常以及危险的参数计算。

好吧,也许这些错误的名字并无那么使人激动。不管如何,我将这些错误归类为与 JavaScript 相关或无关,以此肯定哪些错误能够经过使用TypeScript 来避免。

让咱们一块儿看个例子,使用TypeScript会带来哪些帮助。咱们对所分享的这个 Input 组件进行修改,经过一些设置来还原bug。用户没法提交表单是由于它再也不能经过验证,这是所分享的 Input 组件更改前的简化版本。
图片描述
它接收一个叫onBlur的变量,并将其直接传递给input元素。所作的改变就是添加一个新的onBlur 事件处理。但这里有一个不明显的bug,你能发现它吗?
图片描述
就是事件参数再也不传递给onBlur prop。这就致使在好几个不一样仓库中都出现了这同一个问题。
图片描述
这里 Input 组件做为Redux Form的一部分进行使用,指望获得一个事件或值,以便验证正常工做。若是没有该事件,表单将再也不经过验证,这就意味着提交按钮始终处于禁用状态。

TypeScript在这里是如何帮到咱们的?从文档中咱们能够看到Redux Form有类型捕获约束。
图片描述
onBlur 事件属性必须传递一个事件或值。所以,若是咱们使用了TypeScript下的Redux Form,那么在函数调用那里就能够看到一个当没有传递事件参数时所产生的错误。
图片描述
另外一类常见的问题就是涉及严格的空值检查。即对使用属性来构造或尝试调用可能为null或undefined的内容进行检查。你可能之前有见过这个错误。
图片描述
另外一种是类型不匹配。当咱们尝试使用彼此不匹配的类型时,TypeScript就会提示咱们。
图片描述
因此如今咱们对常见的检查出来的问题有了更好的理解,TypeScript能够帮助预防这种bug。
那整体百分率是多少?(那个事故日志所表现的)38%!
图片描述
咱们发现有38%的事故致使了生产阶段的bug。这些对咱们用户产生实际影响的bug,可使用TypeScript来阻止。这对咱们来讲是个巨大的发现。它有助于将这种(积极)效果转换到现实中。咱们复制了一些BUG事件,并向你们展现了TypeScript所给出的Error提示,而后对bug进行修复(也就是咱们看到的bug提示灯泡灭掉了)。的确,咱们也能够经过写测试代码来捕捉这些,可是经过静态类型检查能够额外增长一层保护层。所以,若是你所在公司有相似的历史,那么你可能就有必要和懂TypeScript小伙伴一块儿来看一下这些问题在大家的代码里所占的比例。

那么团队是否但愿切换到TypeScript呢?咱们在几个团队试用了TypeScript,专门针对以前没有使用过TypeScript的团队来获取更多的使用反馈经验。咱们帮他们设置好 TypeScript 环境,而后收集他们的反馈。在用了一段时间后,咱们向他们发送了一份调查问卷,询问他们是否应继续使用TypeScript。反馈结果是很是确定的。
图片描述
咱们建议使用这种试用期(的形式,其实就是金丝雀模式)来测试新技术或模式。前端工做组的开发也是基于这个形式来进行的,由于它是独立的,它能够很容易回滚到以前的状态。这也对提案颇有帮助,由于咱们能够判断团队是否真的喜欢使用TypeScript。
图片描述
这里可能会有一些关于构建时间上的担心。咱们测量了,发现并无明显的影响。咱们在主仓库启用超过了500条eslint规则,也使用TypeScript eslint解析器。咱们很高兴地发现它们中的大多数均可以工做。若是咱们在未来要弃用TypeScript的话,咱们能够剥离类型,并最终获得大体相同的JavaScript。因此咱们逐一记录、思考、跟进,而且针对提出的问题和担心找到解决办法。

与批评者合做并听取他们的担心对咱们来讲很是重要,最后这些批评者中的大部分转而会支持咱们,咱们的提案也从他们的反馈中变得更为健壮。
图片描述
在充分解决了这些问题以后,针对全部前端工程师进行了咱们是否应该采用TypeScript的调查。咱们收到了确定的回答以后,咱们有足够的证据向前推动,并经过这项提案。

5. 逐步采用

图片描述
在此基础上,咱们逐步扩大了采用范围。此时,咱们已经度过了试验阶段,这对于验证 TypeScript 和打好基础是颇有用的。咱们已经解决了早期的矛盾,并改进了工具和文档,因此以后团队成员会更容易入门。
图片描述
咱们一直有与 TypeScript 团队进行着联系。并帮忙解决一些问题,好比,更好的默认属性优先级处理。在这个阶段,咱们本身内部的 TypeScript 社区也获得了发展。可是大部分 Airbnb 的员工还不知道 TypeScript。这也意味着更多的人能够去帮助和回答他们的问题。接下来咱们将会进入测试状态,团队能够选择使用它。为了帮助团队,咱们建立了内部文档和风格指南,并举办了一些学习课程。咱们创建了一个聊天组,一个内部的类Stack Overflow,谷歌Email主题,github组,来供组内成员交流。咱们想确保人们能获得他们须要的帮助。最后一步是将 TypeScript 彻底普及化。此时就意味着它是稳定状态,每一个人都应该开始使用它。

咱们目前正在努力地去接近这个目标。剩下的步骤就是巩固风格指南、文档、增强内部培训和迁移更多代码。到目前为止,咱们大约有50%的团队使用 TypeScript,在主仓库中,有10%的文件已经被转换成 TypeScript。经过这种渐进的方法,使团队迁移至 TypeScript 的过程更加顺畅。

若是从第一天开始就要求每一个人应该使用TypeScript,那么一个接一个的人就会遇到一样的问题。相反,咱们先在小范围内使用 TypeScript ,而后总结一些经验技巧。当咱们准备把它大规模推广时,这些经验技巧也会用得上。

6. 迁移策略

图片描述
咱们已经探索出了几种将代码迁移至 TypeScript 的方式。咱们最初的迁移策略的是混合使用 JavaScript 、TypeScript。
图片描述
让咱们看看,在主仓库中这个策略是如何进行的。这是我在 airbnb.com 上找到的一个简化版本,而且给它们起了一个比较合理的名字。因此这里不存在公司的隐私信息。

让咱们放大homes 项目,看看使用混合策略转换它会是什么样子。
图片描述
咱们添加了一个TypeScript配置文件,并将各个文件从js重命名为ts或jsx重命名为tsx。若是TypeScript 报错了,那咱们动手去修复他们吧。TypeScript 的一个很棒的特性是,在编译和运行以前,并不须要转换全部代码。这个配置选项(allowJS)容许 javascript 和 TypeScript 文件共存。在这一点上,咱们能够看到网站仍能继续运行。咱们不须要暂停开发而去迁移整个项目,咱们能够挨个迁移文件。咱们会重复这个过程,直到整个项目被迁移。

在关于迁移的话题上,我想花些时间和你们分享一些咱们认为有用的技巧。第一个是$TSFixMe。
图片描述
咱们经过TypeScript的any类型添加了一个全局类型别名,这意味着它能够为任何类型。咱们将它称之为$TSFixedMe,表名咱们在代码向TypeScript迁移完成后,再来将类型修正。平时最佳实践是避免使用any,由于它会形成类型安全丢失,但它在迁移过程当中会颇有帮助。
图片描述
使用@ts-ignore注解能够作到忽略下一行错误。正确地输入一个文件可能涉及一些深层依赖链解析(相似于复杂对象)。咱们能够尝试经过首先转换子文件来避免这种状况,但有时这是不可避免的。所以,$TSFixedMe和@ts-ignore注解可以帮助拆分这些内容,同时则会增长这些检查工做。这些都是暂时的,咱们计划添加类型覆盖工具,并在后面咱们改进类型时提供帮助。
图片描述
在JSX中,咱们在React组件上使用propTypes 进行运行时类型检查。在将jsx转换为tsx的时候,咱们能够删除proptypes直接用TypeScript,也能够在proptypes基础上添加TypeScript。在咱们所分享的react项目中,咱们想保留传参类型,以便别人使用的时候仍然能够得到运行时检查。为了不重复两次类型声明,那就须要与这些类型保持同步。咱们建立了一个Props类型,经过它将给定的propTypes和defaultProps来派生出一个TypeScript类型。这样,propTypes和defaultProps组合并获得这个最终类型。若是你好奇它是如何工做的,你能够查看我在gist上分享的代码片断。
图片描述
最近咱们已经在使用修订迁移策略All-in TS进行实验。让咱们回过来在看看这个Homes项目,而后对它们进行使用all in策略,而后在看它工做怎么样。
图片描述
咱们从js形式的文件开始,咱们把全部的文件都改为TS形式的,而后让项目编译。可能咱们使用一些比咱们想要的更宽松的类型,但其实咱们已经开启了TS最严格的检查配置。
图片描述
而后咱们接下来再继续改进类型,移除ts fix语句,好比@ts-ignore(@ts-ignore 注释隐藏 .ts 文件中的错误)。这与js和ts混合策略相比起来有一些优点。经过类型逐步改进比经过文件逐步改进更为简单(两种策略的对比)。若是你正在开发一个新功能,你只须要关注新添加的类型,而后简单的修复这个它便可,而不是先转换整个文件来修复全部错误,而后再添加你所须要类型。

不用重命名文件也意味着更方便查看。有时候,若是一个文件在一次提交被重命名,而后在别的提交中修改。他们会在code review中单独出现,程序员必需要合在一块儿看才能知道变化了什么。后一种策略还能清楚地知道缺乏哪些类型。

TypeScript类型推导能力十分强大,咱们能够在编写代码的时候大量使用它。为了经过编译,有些文件须要进行一些TS Fixed。TS就能够推断出剩余部分。

还有就是开发者们可能有一个固定的思惟模式,他们并不会根据文件扩展名来切换思惟,因而就出现了好比为何我不能在这里添加类型?为何我不能在那里获得编译错误的疑问(.js和.ts混用)?那些类型在全部文件中均可以添加、使用、检查。

这听起来很不错,可是咱们该如何迁移咱们整个代码呢?对于大规模代码修改而言,Codemod是一种十分强大的工具。拿最简单的形式来讲,就比如是咱们在咱们的项目中所使用的全局搜索和替换。你也许在你以前的IDE里面干过这件事(全局查找和替换),这些Codemod库能够经过正则来替换,但它们很不稳定,可能会因细微的代码风格变化而终止。
ast.png
或者咱们可使用以前某人已经讲过的抽象语法树。so,这就是这段代码用AST(抽象语法树)来表达的形式。如图所示,左侧的代码都一 一对应着右侧抽象语法树上的节点。因此为了好玩,咱们想写一个Codemod来反转代码中的全部标识符。咱们将咱们的代码做为输入参数,根据这个建立出AST(抽象语法树),修改AST树而后产生新的代码。这里的关键是咱们以编程方式进行此更改。若是你手上须要修改的文件数并很少的话,咱们能够一个个的去修改。但若是一旦文件数量达到数千个以上,这种手动去修改的想法可能会使人感到十分心累。
fb1.png
所以咱们Airbnb采用了Facebook的Jscodeshift来进行这种大量的代码重构。这个转换库能够捕获咱们刚刚对该ast进行的修改而且反转标识符。咱们找到与标识符对应的全部节点,用名字反转,用新节点去替换这些节点,而后获得新的代码。Missy Elliott(歌手)也将会咱们感到自豪,因此咱们反转了它。

咱们拿到了代码而且从新改装,找到了成员的标识符而后翻转它。yeah!
ast1.png
astexplorer.net这个网站没法帮你掌握好说唱技巧,但能够帮助你查看你的Codemods。在这个网页下,它有一个能够经过源代码输出对应的AST树的功能,以及在你对代码的改变同时反映到AST树上。

我也在DefinitelyTyped 这个库提交了关于Jscodeshift的PR,这样的话能够来下降你们在使用TypeScript与Codemod的交互门槛。

在将JavaScript代码迁移到TypeScript时,有这几种模式。对于react组件,咱们一次次的将静态类属性移动到class body里面。建立一个PropsType表示react生命周期方法。咱们将它们编码为Codemod,以便咱们能够在更多代码上重复运行它们。咱们经过使用一个叫做TS Migrate的工具来将它们进行打包。这个工具的功能是当如一个JS项目,而后获得一个编译好的TS项目。随着时间的推移,你仍然须要慢慢找到类型,但它为你提供了一个工做前提。咱们将此工具应用于咱们的内部分享的React组件库,如今在咱们的网站上已经频繁地使用。咱们有内部的类型定义库(DefinitelyTyped),可是由于react分享组件库的快速发展,因此作到与时俱进地更新太难了。因此,咱们想直接从源码类型出发,这也是咱们迁移TS的第一个目标。咱们已经将超过3万行以上的代码都进行了TypeScript化,大家可能认为咱们整个团队花了四周的时间才能完成这个。事实上,咱们用了一套咱们本身的Codemod工具,仅需数分钟就完成了。

咱们使用来自proptypes的类型信息,同时使用$TSFixMe,并基于此来继续进行优化。但即使如此,咱们也生成了有意义的TypeScript声明文件,这样咱们能够在其余仓库中进行使用。在这个例子中,咱们能够看到须要合并的代码行数多的有点可怕。经过使用TypeScript编译器以及在可视化回归测试的帮助下,咱们将在CI上运行测试。经过这些测试我能够很自信的说,个人这些改变不会对原来的系统产生任何不利的影响。固然咱们还能确保咱们的站点仍旧在正常工做,并不须要回滚代码。难以想象!

咱们如今已经将TS Migrate运用在其它的一些地方,同时也在不断优化和迭代它。咱们计划在之后会将它运用于更多的代码上(JS代码)。咱们打算以后将它开源,这样大家也能将它运用在大家的本身的代码迁移上。

我想给你一些咱们能够从TypeScript迁移中得出关键点,而且是能够普遍应用的。在大型组织中实施变革多是一项挑战,但强有力的事实依据和相关问题和担心的解决,可使咱们信服。采用逐步变化的方式有助于减小摩擦并证实其价值。一条明确的迁移路线能帮助团队更好的转向新的模式,同时好的工具也能促进这个过渡的过程。

我之因此开始这个工做,是由于以前有个产品组对个人工具感到失望。当我得知公司内部其余人也有这种改变的想法的时候,我便与他们合做并将之进行下去。与其怨天尤人接受现状,只有经过行动才能发生积极的改变。因此我鼓励你去追求那些可让你对组织充满激情的事情,让你和你周围的人的生活变得更好。

感谢你们的倾听,同时感谢AirBnb为这个项目做出贡献的每个人,尤为是台下的Joe和Mohsen。还有对其余一些优秀的Airbnb工程师表示感谢。我手上也有些TypeScript主题的小便签和一些钥匙链,先到先得,只限前30人。

感谢你们的倾听

相关文章
相关标签/搜索