关于 React :你不可不知的 19 件事儿

阅读数:18 2019 年 12 月 30 日 23:25

关于React :你不可不知的19件事儿

这篇文章是我个人对本届 React Conf 的一些精华总结。所有讲座都值得一看,所以我建议大家回顾第一天和第二天的所有录像。我在文末给每个讲座都注明了视频时间戳。

总结的有些内容可能会让你感到惊讶,其实我也是!这些内容并不都是技术主题的,但是都贯穿着同一条主线。

一切都是为了服务于开发者体验

Tom Occhino 提到这个话题后我就一直在思考它。其实所有演讲中都能看到这种思想的影子,这也是为什么我如此热爱开发工具和前端的原因所在。

React 旨在创造一种开发体验,使开发者们能够更加轻松的学习并实现更加强大的功能,通过更快的迭代来提高生产力,以及提升开发软件的规模。这些事情使我像 React 一样。我觉得 Facebook 在交付方面做得很好。

这一切有什么意义呢?很简单,我们做这些就是为了改善用户体验,为了提升用户的生产力。我们要帮助他们,让他们用优雅的方式来实现目标。虽然我们背后要做的工作可能不会都那么轻松,但我们应该让用户感受到如沐春风。

由于 React 是一种网关技术,而且有 63%的 JavaScript 开发人员正在使用它,因此他们的团队非常重视社区。他们通过了《贡献者公约》,欢迎大家提出批评。作为一个社区,我们应该能够接受批评,而不是处处为自己辩护。Elbert Hubbard 说:“不想要批评的话,除非我们什么都不说、什么都不做、什么都不承担。” React 所做的事情以及其原因才是关键所在。这自然会招致批评,进而让技术不断成长。

令人欣喜的易用性、性能和并发模式

你在使用 React 时是否遇到过焦点问题?我就遇到过。焦点确实很重要,原因有很多,它可以帮助人们浏览页面,这对不用鼠标的用户来说非常重要,稍后会再讨论这个话题。总之我们很高兴看到 React 团队准备将可访问性纳入架构之中。

大会期间我思考最多的事情之一就是性能。Facebook 要处理的一些性能问题是我们大多数人永远都遇不到的,但他们从这些教训中所学到的知识仍然可以用来改善用户体验。重点在于感知到的速度,而不是单纯的页面加载延迟。

Yuzhi Zheng 在她的演讲中讲了选择性 Hydration 的例子。你可能也听说过 Suspense,它能改善 Web 上的整体用户体验。

并发模式

假如我们要制作一个关联用户输入的可过滤列表。用 React 来做时,除非你不在乎性能,否则你就得对列表的更新做防抖和优化。

并发模式能让 React 对低优先级的工作中断响应,从而提升 React 应用的响应速度。这使得用户输入之类的事情,比重新渲染列表之类的事情具备更高的优先级。React 将可以同时处理多个状态更新,这将帮助我们消除卡顿和过于频繁的 DOM 更新,还可以让我们能优先处理悬停和文本输入之类的交互,我们知道用户希望这些内容能够快速的被处理处理。

React 团队分享了许多并发模式的示例,建议参考( https://reactjs.org/docs/concurrent-mode-patterns.html )。

CSS-in-JS-at-FB

Frank Yan 宣布 Facebook 正在构建自己的 CSS-in-JS 库,我很感兴趣。刚开始我觉得难道我们目前的库还不够吗?这使我们有机会进一步了解 Facebook 面临的一些扩展问题,以及他们解决问题的创造性方式。

CSS 的维护工作很容易失控。来看下面的例子:

复制代码
.blue { color: blue; }
.red { color: red; }
复制代码
<span class="red blue">
Which color will I be?
</span>

示例中,我们希望文本结果为 blue。因为这个类在类声明中排第二,因此我们应该可以认为它具有优先权。但事实并非如此。.red 类在层叠样式表中排第二,所以会优先展示为红色。如果这些类位于不同的样式表中,则它们在页面中的加载顺序就会很重要。
这个例子很简单,但这么简单的问题也会失控。Facebook 想靠他们的新的库来解决特异性战争、主题化和可访问性之类的问题。

演讲中的一些有趣的细节:

  • 开发人员能以像素为单位来编码,然后在 REM 中编译;
  • 他们通过实现类型检查(捕获和修复错字、检测和删除未使用的样式、避免跨浏览器陷阱)来提升安全性;
  • 向开发人员显示可访问性错误;

关于React :你不可不知的19件事儿

  • 组件可以有能被覆盖的默认样式(包括类型安全!);
  • 重复规则会被移除,这样 CSS 文件就能瘦身了(Facebook 最近重写了前端,从 413kb 瘦身到 74kb)。

原子 CSS

每个类都创建一个唯一的属性值,这是用来优化组件的。( https://youtu.be/UxoX2faIgDQ?t=4939

复制代码
.c0 { color: blue; }
.c1 { color: red; }
.c2 { font-size: 16px; }
复制代码
// Generated Component (Pre-Optimized)
const styles = {
blue: {color: 'c0'},
default: {color: 'c1', fontSize: 'c2'},
};
function MyComponent(props) {
return (
<span className={styles(
'default',
props.isBlue && 'blue',
)}>
Hello World!
</span>
);
}

这个例子展示了 CSS 怎样原子化,它还展示了如何使用 props 设置 span 的颜色,但这段代码还能进一步优化。

复制代码
// The styles block is no longer needed
function MyComponent(props) {
return (
<span className={styles(
(props.isBlue ? 'c0 ' : 'c1 ') + 'c2 '
)}>
Hello World!
</span>
);
}

这些东西非常有趣,我期待能尽快看到他们发布这个库。

数据驱动的 JavaScript

想让页面看上去更快、交互更迅速吗?Ashley Watkins 也在思考这个问题。她提醒了我,可以考虑用调整数据获取的方法来改善用户体验。我本就对 Relay 感兴趣,听了她的演讲就更跃跃欲试。

分段代码分割

Facebook 一直在设法加快 FMP 的速度。他们的一种方法是“分段代码分割”。

使用这种方法时,你可以一次下载并分段交付。拿 Facebook 帖子来说可以分为三段。

  1. 载入中
  2. 显示
  3. 分析工具

每个阶段都可以有自己的代码获取和渲染过程。FMP 所需的所有数据都可以在加载阶段获取代码的时候同时获取。

关于React :你不可不知的19件事儿

应该关注首次有效渲染的时间

为了尽可能优化用户体验,你应该关注的是首次有效渲染(FMP)所需的时间,其实这就是主要内容显示在页面上所需的时间。有许多指标可供参考以缩短加载时间,但 FMP 是非常关键的一个。

你可以在 Relay 中使用 GraphQL 进行流式查询。你可以将某些数据标记为关键数据,而将其他数据标记为非关键数据。然后你可以先从服务器获取最重要的内容,并在获取其余数据的同时开始显示内容,这样你就可以在内容到达时立即开始渲染。

数据驱动的代码分割

这个惊到我了。Relay 太强了,无可争议。Relay 有一项新功能,可让你扩展查询以查看渲染特定数据类型时所需的组件代码。你可以将代码视为数据。当服务器解析你的 GraphQL 查询时,它可以让客户端知道后者将需要下载哪些组件代码,从而可以更快地获取它们。

Ashley 的演讲太棒了,她承诺说这些仅仅是个开始。我还没使用过 Relay,但我迫不及待想开始了,相信你也会这么做(尤其当你了解了它更多的能耐后)。

解决全球的饥饿问题

大会第一天主要谈的是技术话题,讨论生态系统即将到来的众多功能如何改善用户体验。

Tania Papazafeiropoulou 则一转话题,开始探讨全球范围的饥荒问题,以及她正在研究的一种很酷的产品 OLIO。它可以帮助人们分享食物,而不是浪费食物,而且它是由 React 打造的。

这个演讲并没有大肆宣传 React 的新功能,但它让人们更加认识到 React 的力量根源所在。React(和 React Native)使 Tania 的团队能够快速构建产品,并开始对社会产生积极的影响。

改进 REST 的体验和安全性

RESTful API 并不是一个新的热门概念。它们是在 2000 年正式定义的,自从它被定义以来已经取得了显著的成绩。话虽如此,REST 确实有一些值得挑战的问题。

Facebook 用 GraphQL 回应了这些挑战。GraphQL 为我们提供了对数据的可理解的定义。它可以让客户只获取所需的内容。这样渲染速度就能显著加快,因为你不必下载太多数据。

Tejas Kumar 很好地总结了新旧技术的差异:

https://youtu.be/UxoX2faIgDQ?t=9586

REST

  • ❌没有规范的数据格式
  • ❌猜谜游戏(不允许的请求会以 400、401 还是 404 响应呢?)
  • ❌无意义的对话
  • ❌无合约协议

GRAPHQL

  • ✅规范的数据格式
  • ✅没有猜谜游戏
  • ✅有意义的讨论(影响用户的事物)
  • ✅强有力的合约协议

许多人都喜欢 GraphQL,但有时它不是 API 的选择。Tejas 和他的团队开发了一种工具,可以消除 REST 的一些陷阱。它包括 Swagger 的代码生成和 OpenAPI 规范。

React 的引擎(构建自定义渲染器)

如果你曾经演示过以前编写过的代码,肯定知道渲染器经常会出错。于是 Sophie Alpert 就在演讲中教我们怎样构建一个 React 渲染器。

我不觉得我是 React 专家(目前不是)。我从没看过 React 代码库,觉得那肯定很难吧。但随着我继续学习和掌握 React,最终还是到了研究代码库的时候。不过看 Sophie 在 React Conf 讲台上现场构建一个自定义渲染器,似乎也没那么难嘛。

本地化(一件很重要的事)

我们这些母语是英语的开发人员做产品时往往不会首先考虑本地化问题。值得庆幸的是,还好我意识到了这一点,以后会更认真地对待它。

我们总觉得用户和我们一样,所以没那么关心本地化的问题。可现实中用户并不是都和你一样的!这就是为什么我们需要用户测试、获得用户反馈,并做出更加包容的产品,适应所有的用户类别。

去年 Nat Alison 提出了一个问题:“React 有其他语言版本吗?”当时的答案是否定的。

为什么这个问题如此重要?Nat 总结得很好。如果只有说英语的人才能使用 React,那么会有多少人没法使用这些工具来开发出令人赞叹的产品?如果只让说英语的人来塑造我们的数字世界,我们的损失得有多大?世界上只有 20%的人口英语。如果我们不帮助其他语种的人们使用 React,将来只会自讨苦吃!

关于React :你不可不知的19件事儿

Nat 和几千同行在过去一年中取得了难以置信的成就。但还有更多工作要做,如果你会两种语言,可以为此做出贡献。

https://isreacttranslatedyet.com/

无障碍马拉松

就像本地化一样,可访问性 / 无障碍功能可能也不是轻松的活计。开发无障碍功能时,你需要改变思考方式。你必须考虑更多的受众,考虑可能与你不同的人群。有时候这很困难,但我们都能做到。

跑马拉松是另一个可能很难做到的例子。根据 RunRepeat 的资料,2018 年有 1,298,725 人完成了马拉松比赛。这并不是他们天生就能做到的事情,他们是一步一个脚印,最终实现了自己的目标。

我们面对无障碍功能也要有这样的精神。一步步来,就不会觉得那么不堪重负了。React Conf 吸引我的其中一点就是从新的角度学习软件开发和了解世界。Brittany Feenstra 的演讲又一次扩大了我的视野,让我更深入地思考无障碍功能。

我不会在一夜之间完成无障碍马拉松,但可以每天一点点向前推进。值得庆幸的是,在此过程中有很多好工具可用:

https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd
https://github.com/FormidableLabs/eslint-plugin-react-native-a11y

你不需要 Redux(对吗?)

在 2019 年,管理 React 状态有许多方法可用(甚至包括 easy-peasy):

https://reactjs.org/docs/react-component.html#setstate
https://reactjs.org/docs/context.html
https://reactjs.org/docs/hooks-intro.html
https://github.com/reduxjs/redux
https://github.com/jamiebuilds/unstated
https://github.com/mobxjs/mobx
https://www.apollographql.com/docs/react/data/local-state/
https://easy-peasy-v3.now.sh/

选择困难症又犯了。然而哪个选项才是正确的取决于你的情况。请记住,开发人员体验是服务用户体验的。因此,我喜欢用尽量简单,能随我的原始决策改变而变化的方式来管理状态。

很高兴看到 React 现在内置了很多选项。你可以使用 Context 和 Hooks 做很多事情,而无需其他依赖项。

为了快速行动并完成目标,你必须愿意放弃以前完成的工作。要让你的产品与众不同,你需要爱上重构并走出舒适区。我认为管理 React 状态的许多选项都反映了这一点。有些选项需要很多样板,有些则不需要。有些是构建好的,有些不是。跟着感觉走,将来随时按需调整。

梦回 1999

这场演讲的标题就很吸引人。1999 年的编程是什么样的?那时我才九岁。有些人九岁就开始写代码,可惜我不是其中之一。

这场演讲也是必看的。Lee Byron 送上了一段精心打造的内容。他带我们回到了 PHP 和 LAMP 堆栈成为 Web 开发工具的年代。他向年轻人讲述了 Facebook 开发 React、GraphQL 和 Relay 等工具之前走过的那些历程。对于老一辈来说,那是值得怀念的时光。

我一直都很尊重 Facebook 所做的工程工作,而这场演讲捋清了背后的成因。使用 React 就好像是一种特权,现在我知道这种感觉来自哪里了,正是 Lee 这样的人为社区所做的一切激励着我们前进。

连开发工具都和 UX 相关

第二天的第一场演讲是 Brian Vaughn 做的。用 React 构建内容的开发人员大概都用过 React Dev Tools。它们帮我摆脱了许多我自己制造出来的混乱局面。

React Dev Tools 得到了全面的重写,为我们带来:

  • 更好的性能
  • 新的 API 支持
  • 新的 UX 功能

看看,连开发工具都专注于提供出色的 UX!

一项让我印象深刻的改进是帮助团队浏览之前不熟悉的项目。我们没接触过的代码看起来肯定很陌生,但我们也都知道哪怕是自己写的代码时间久了也快不认识了。现在,我们可以看到 props 如何流过组件,可以过滤组件树,可以深入检查组件以及如何将 hook 与 dev tool 一起使用。另一个好东西是 suspense 开关。这个功能看起来很简单,但价值不可估量。

再加上共享配置文件的改进,新的开发工具让我们更容易找出渲染事物的起源。第三方也有类似的工具,但现在我们可以直接在开发工具中了解渲染背后的内容了。

其他内容还有不少,建议大家自行探索。

https://github.com/welldone-software/why-did-you-render

可 Suspense 的数据(Relay 太棒了)

我在一个小项目里一直在使用 GraphQL,而且非常喜欢它。接下来我得试试 Relay,看看它们的组合有怎样的能量。

React Suspense 的宗旨是让我们能够先显示某些 UI,而无需等待所有 UI 准备就绪。

来看一个组件的基本示例,该组件在获取数据时显示加载状态(使用 Suspense):

复制代码
const Composer = (props) => {
const data = useQuery(graphql`
query ComposerQuery {
me {
photo {
uri
}
}
}
`, variables);
return (
<div>
<img src={data.me.photo.uri} />
</div>
);
}
const Home = (props) => (
<Suspense fallback={<Placeholder />}>
<Composer />
</Suspense>
);

在此示例中,我们有一个 Composer 组件,该组件可获取个人资料图片的 URI,然后显示它。你可以看到,在 Home 组件中我们将 Composer 包装在一个 Suspense 块中。然后在加载数据时将渲染 Placeholder。这种模式可以被视为“渲染时获取”。
这种模式的加载顺序如下:

关于React :你不可不知的19件事儿

如你所见,这使我们能轻松处理数据加载。我们可以回退到 placeholder 或 spinner 等加载组件来提供更好的用户体验。

上面的模式提供了很多好处和灵活性。但 Facebook 团队认为,想要找出组件所需的数据时没必要先渲染组件。为了解决这个问题,他们开始使用一种称为“获取时渲染”的模式。

为了实现“获取时渲染”,Facebook 团队将 useQuery 分解为两部分,分别是 preloadQuery 和 usePreloadedQuery。这到底是什么意思呢?

preloadQuery

该 API 将获取数据并为结果提供参考,但它没有提供实际数据。

你将在显示新用户界面的那个事件处理程序中调用此 API。例如,如果用户单击链接,触发导航到新页面的动作,则我们处理单击的事件处理程序将使用 preloadQuery。另一个例子是将鼠标悬停在某处以打开工具提示。onHover 事件处理程序将调用 preloadQuery。

usePreloadedQuery

该 API 接受 preloadQuery 调用的结果。实际上它本身不会获取任何数据。它查看 preloadQuery 的当前状态。如果准备就绪,它将显示结果;如果尚未准备就绪,它将暂停;如果查询失败,则可能引发错误。

无论发生什么情况,usePreloadedQuery 都不会触发新的获取,这使其高效且可预测。

使用这两个 API 代替 useQuery 后,加载顺序如下所示:

关于React :你不可不知的19件事儿

强烈建议大家听听 Joe Savona 的演讲。他总结得更好更深入。这是我最喜欢的演讲之一,因为我对这种模式所带来的可能性感到非常激动,并且迫不及待地想尝试一下。

如小说般的 React

Jenn Creighton 发表了我最喜欢的哲学演讲。她从事创意写作工作。创意写作一直是我的最爱。像 Jenn 一样,我曾经幻想成为作家。她在演讲中解释了一个概念,这个概念以一种有趣(且出乎意料)的方式转化为编码。

展示,不要讲述

我们看一下传达相同含义的两种方法(Jenn 讲的例子)。

她累了。

她的步伐愈加艰难,一点点挪向床边,身体也越来越沉重了,最后一头扎进了被褥里。

表达她累坏了,同样的意思,哪种表达更有力量?答案自不必说。我们软件工程师经常会陷入讲述的陷阱里。我们抽象、抽象、抽象,直到抽干一切。

大多数情况下,我会尽力避免代码重复。这条原则自有其意义,但就像写作不能循规蹈矩一样,有时我们也需要打破软件开发的规则。比较一下两段结果相同的不同代码。

复制代码
const Nav = ({ links }) => (
<nav>
{
links.map(link => (
<Link to={link.to}>{link.name}</Link>
))
}
</nav>
);
const Header = () => {
const links = [
{ name: 'Home', to: '/home' },
{ name: 'Settings', to: '/settings' },
];
return (
<>
<Nav links={links} />
</>
);
}

这看起来很好用,但如果我们想添加一个导航项该怎么办? 例如一个登录按钮。

复制代码
const links = [
{ name: 'Home', to: '/home' },
{ name: 'Settings', to: '/settings' },
{ name: 'Login', to: '??' },
];

我们的 Nav 组件无法处理这种情况。我们可以很容易地实现一种处理它的方法,但这也很容易失控。我们可以重构 Nav 组件,变成像这样:

复制代码
const Nav = ({ links }) => (
<nav>
{
links.map(link => {
return link.to
? <Link to={link.to}>{link.name}</Link>
: <a onClck={link.onClick}>{link.name}</Link>
})
}
</nav>
);

这样也行,但我们得涵盖多少边缘情况?最后 Nav 组件的推理会不会太过复杂? 我们可以用另一种方式重写 Header 组件。

复制代码
const Header = () => {
const links = [
{ name: 'Home', to: '/home' },
{ name: 'Settings', to: '/settings' },
{ name: 'Login', to: '??' },
];
return (
<nav>
<Link to="/home">Home</link>
<Link to="/settings">Settings</link>
<a onClick={onSelectLogin}>Login</a>
<nav/>
);
}

我简化了 Jenn 演讲中的例子,但应该足够说明问题了。第二个 Header 组件更容易推理。尽管我们现在可能会重复一点东西,但这并不会有多大坏处。如果我们想将 Icon 组件添加到其中一个链接中,则不必再处理 Nav 组件中的所有极端情况,只需将其添加到所需位置即可。
Jenn 还引用了 Neil Gaiman 的妙语,我得给大家分享一下。

请记住,为了做到完美,你迟早要放开当下的执念,然后继续写下一篇。

在构建心理健康写作平台 Wrabit 的时候,我一直在追求完美。有时候这让我觉得自己不像一个开发人员,有时候让我感到疲倦。最后我就只去关注那些自己可以理解、可以交付、将来随时可以重构的内容了。

正如 Jenn 所说,重构并非失败。

UX 驱动的流体动画

我做过的动画不算太多。我设想的未来中,我将从 Dribbble(动画之类的东西)中获得出色的 UI 设计,并用在实践中。动画绝对是优秀设计的重点所在,但我们玩动画时也得牢记用户体验。

Alex Holachek 的演讲也拓宽了我的思路。我喜欢 UI 交互,它们让我的内心感到温暖舒适。但我并没有考虑到所有用户,这是我的错。

对于使用诺基亚 6 的用户来说,精美的动画该怎么展示出来呢?毕竟大多数人都会选择购买中端机型甚至低端机型,而不是新款 iPhone。

Alex 的演讲让我更多去考虑大众的水平。平均的设备性能,平均的数据带宽,等等。为大众打造的产品,高端用户是不会觉得不满的。

另外,event.preventDefault() 会对滚动产生不良影响。

真实的迭代体验

今年的讲座内容千姿百态。Luca Demasco 与 Zach Rispoli 合作开发了 Wick 编辑器,向我们展示了迭代的过程,很新奇的话题。

Wick 编辑器是一个免费的开源工具,可用于创建游戏、动画以及你能想到的任何东西。当 Luca 展示当前版本时,UI 确实给我留下了深刻的印象,看起来很直观,并且有很多功能。但一开始并不是这样的。

Luca 和朋友通过不断的迭代才达到了今天的状态。他们也不只是为了迭代而迭代。他们将 Wick 带入了许多环境(学校、图书馆等),并在许多类型的用户(初学者、中级、年轻人、老人)面前展示了界面。他们采取了获取,保留,扩张 (Laser-Focused Approach) 的方法,并收集了大量反馈,才让 Wick 有了今天的成果。

在我考虑如何迭代自己的产品时,这种真实的过程给了我启发。如何快速启动项目并有目的地迭代?我没有那种体验,所以有时候会缺少信心,但我很希望能享受这个过程。看到像 Luca 这样的人分享经验使我备受鼓舞,我感谢大会上所有人的真诚分享

简单事物的复杂性

你是否在用 react-select ?没用过?其实你可能只是没意识到而已。

这个组件在 GitHub 上有超过 18000 个星,每周有 150 万次下载,好多啊。

你可能不会想到一个简单的 React 组件会有那么复杂。Jed Watson 开发了一个漂亮的 React 组件,并很好地实现了它的目的。它有很多功能,并且开箱即用。

Jed 走过了一段漫长(有时很艰苦)的道路,终于打造出了今天的 react-select。他就开发广泛流行的开源项目的过程分享了深刻的见解。他还告诉大家,为什么一件简单的事情往往可能非常复杂。

当 Jed 展示了 react-select 到 v2.0 的演变时,我受到了启发。他重申了重构的重要性,还讲到如果你不再追求完美,就能做到很多很棒的事情。

优雅的透明度

政府支出是一个重大话题。我们应该看到我们的税收去向,以便让政府负责。

Lizzie Salita 证明了政府网站可以高效、易用且美观。当她演示 USAspending.gov 支出浏览器时,我真的很惊讶。再对比其加拿大版本,你就能知道 React 可以为用户体验带来多大的改变。

我正在慢慢开始涉足政治领域。尽管我一直在努力获取信息,投出负责任的选票,但需要做的事情还要很多。诸如 USAspending.gov 之类的工具能让参政变得更加轻松有趣。我认为我们应该继续开发这样的工具,让每个人都能了解最新情况,以便所有人都可以参与塑造我们的未来。

奇迹驱动的开发

大会的最后一场演讲真让我震惊。Alex Anderson 构建了一个疯狂而复杂的飞船模拟器,称为 Thorium

Thorium 模拟器使诸如狮门航天中心之类的许多组织能够为各种人群提供 STEM 相关的活动。这些空间中心使学生能够通过高度互动和富含教育意义的小组活动来成长。

React Conf 上的很多演讲和与会者所做的优秀工作,是由美好的愿望驱使的。Alex 的工作之所以如此突出,是因为他的热情从他所说的每句话中迸发出来。他热爱自己的工作,并且是一位非常有才华的工程师,他正在使用现代技术来为儿童和成人建立良好的体验。

Alex 的演讲最打动我的是奇迹驱动开发的概念。你是否想过技术的可能性?想过你自己身上潜藏的能力吗?

这些问题驱使我们建立有趣、愉快和真正美好的体验。这些问题促使 Facebook 的工程师和世界各地的公司利用技术来塑造我们的世界。

我在今年的 React Conf 上,从所有人那里学到了很多东西。我很高兴能参加大会,感受到的是满满的惊奇与兴奋。

我迫不及待地想看到这种感受能驱动我开发出怎样的奇迹!

原文链接 https://blog.anthonymorris.dev/19-takeaways-from-react-conf-2019

评论

发布