React Conf 是 React 官方主办的年度盛会,2020年因为疫情没有举行,2021年的更加被期待,也是第一次线上举办,同往年我熬夜看完 Keynote。整个大会一共19个主题,5个半小时(包括1个小时茶歇),其中有将近40%的女性演讲,内容涵盖 React 18新特性,未来前瞻,以及生态的内容,接下来是我的一些感受。(完整回放链接:https://www.youtube.com/watch?v=8dUpL8SCO1w)
内容概要
React 新特性介绍
Suspense
首先第一个重点介绍的 Suspense,它并不是新特性,React 16.6 就已经引入,现在主要是用来和 React.lazy做组件延迟加载,这次介绍的重点是用 Suspense 来做取数。它最大的优势是把取数和loading处理的关注点分离。
对我而言,Suspense 能够把取数这类异步操作变成同步代码开发一样的体验。确实能提高代码的可读性和可维护性。Suspense 本身并不处理取数,需要和取数库搭配使用,目前 Relay 官方支持,swr 也已经支持,我最期待的 redux-toolkit 也有了支持计划。
Concurrent
然后开始讲比 Suspense 更底层的 Concurrent。
React 引入的并发最开始叫 Concurrent mode,也就是走新旧二选一的“模式”,确定把 mode 改成 feature 目的是你可以在想要的时候开启,不开启依旧是旧模式。这样对于大型应用就可以逐步升级。
React 18中所有新特性就这一页就可以概括。
- Automatic batching:React 17 的批量更新只在事件回调中才会生效。这次 React 18 后对 Promise、setTimeout 等同样会做自动批量更新。
- startTransition & useDeferredValue:这里的 Transition 并不是动画的API,而是用来降低渲染优先级。分别用来包裹计算量大的 function 和 value,降低优先级,减少重复渲染次数。
- useId:是一个生成唯一 dom id 的 hooks。为了唯一的 id,最简单的做法是随机数,但这样的问题是 SSR 的场景下前后端生产的 id 不一致。useId 就是基于 DOM 的层级结构来生产唯一 id,确保前后端一致。
- Streaming SSR with Suspense:可以让 Suspense 在 SSR 后端生效,多个组件渲染的场景下,不需要所有组件都完成再吐给前端,而是可以部分完成后 streaming 流式的返回给前端。具体 streaming 的实现是轮训还是 web socket,我还不知道。。介绍完这个特性以后,有 Shopify 来站台分享,他们做了 Hydrogen这个框架加速自建商店的开发。基于 React Server Component 和 Suspense for SSR 这些。有 Shopify 的支持,Hydrogen 这个框架值得期待,但这个妹子的 PPT 准备确实一般。
彩蛋:千亿美金市值大佬 Shopify 创始人兼 CEO Tobi 直播写 React Hydrogen【这大概是最富有的前端开发者】 https://youtu.be/FPNZkPqUFIU
useSyncExternalStore
还有一个上页 PPT 没有讲的内容是 useSyncExternalStore。它是用来解决 Concurrent 下并发渲染数据撕裂(Tearing)的问题。
开发第三方 Store 类库的时候会遇到这个问题,解决的方法是使用 useSyncExternalStore。
总之,这也是引入 Concurrent 之后带来的新问题。目前 React-Redux V8 alpha 基于 useExternalStore 实现,并用 TypeScript 重写。https://github.com/reduxjs/react-redux/releases/tag/v8.0.0-alpha.0
React Without Memos
第一环节最后一场是黄玄带来的 React Forget 分享,有点 One More Thing 的味道。
非常精准的解决了 React 引入 Hooks 后 memo 满天飞的痛点。是否要 memo 是争议很久的问题。可以不写,但会导致很多不必要的渲染导致性能问题。所以为了性能考虑有些人会建议一股脑全都 memo,React Forget 的解决思路是用编译器分析源代码,把数据和函数都放到内置的 memoCache[] 中来自动 memo,减少多余渲染,这真的是一个好主意!
但我觉得想法很理想,做起来困难重重。本来不希望 memo 的自动加了怎么办,搜索一下代码中有多少处使用了 eslint-disable-next-line react-hooks/exhaustive-deps 来避免 useMemo 检测依赖。老项目如果添加如何做全面的测试,出问题如何排查。
目前 React Forget 还处在探索阶段,黄玄说未来也有可能“失败”,但这个特性还是很期待的,期望 React 团队能找到的解决办法。
React 版本演进图
这个图很全面,React 19 的重点的方向表达清晰。对于 data fetching 和 SSR 会非常重视。
React 18 升级方法
// before const container = document.getElementById('root'); ReactDOM.render(<App />, container); // after const container = document.getElementById('root'); const root = ReactDOM.createRoot(container); root.render(<App/>);
只需要删2行,加3行,一共5行代码搞定。
一杯咖啡的时间全部搞定,至于依赖库的升级花费多少时间,就看你咖啡时间的长短。
实际上,我最好奇的是:
为什么能做到大平滑升级?- React working group
这个分享介绍了 React Workgroup 建立的过程,方法,做了哪些事情。
感兴趣的可以去关注下他们 github 的讨论区,很专业也很热闹 https://github.com/reactwg/react-18/discussions
如何降低新手的学习成本
经常有人说前端概念太多,学不动了。React 一直很关照初学者。
目前 React doc 有 200万的 MAU,开发者众多。
React 这几年编码方式经过多次变化,React.createClass -> class -> hooks
未来都会转向 hooks,但原来的文档组织方式的问题已经难改变。
于是干脆进行了一次彻底的基于 hooks 的重写,并在文档中加入可实时修改运行的代码,还有一些小测验。读起来非常利于新手由浅入深 https://beta.reactjs.org/
Relay、React Native 以及周边
Relay 和 GraphQL 的更新
Relay 从发布后吸引了很多的关注,后来一直不温不火。但并没有停止更新。
上面分享者说 Facebook 使用了 Relay 后处理网络数据的时间减少了 10 倍。(到底是开发时间还是执行时间,我没有搞清楚)
当然,出来讲了,肯定有大的更新。因为旧的 GraphQL 不够模块化,他们基于 Rust 做了重写!性能提升 5倍!95%的分位值提升了 7倍!
React Native
比较特别的点是请来了 Facebook Messenger 团队和 微软一起来分享。
Messenger 旧的应用是 Electron,他们把它迁移到了 React Native,过程中复用了大量的代码,并没有重写。最终打包体积减少了 80%,冷启动时间减少 60%。
关于微软,已经不是曾经的微软,现在变得很开放。比较好奇的是为什么微软会选择 React Native?
原因如上,JS 是目前使用人数最多的语言!React.js 是最流行的前端开发框架!
微软使用 RN 的场景主要是 Office 中的评论模块、XBox console、Power Apps。
不是纯粹的拿来主义,微软维护了 react-native-windows react-native-macos。
RN 完美吗?
RN 在计划新一轮的重写,从 async 变成 sync,充分利用 concurrent 的优势。
React 愿景
Keynote 的最后的环节 Andrew Clark 分享了 React 的多平台愿景。除了老生常谈的“Learn once write anywhere”,“React is more than a library. React is a paradigm for building user interfaces”。今年做了一个概括,我总结为以下的 PPT(Andrew 并没有 ppt):
React 过去和将来的工作都是围绕如何移除错误的选择(Rejecting false choices),以此来让用户无需选择就有最好的体验。实际上围绕3组角色和场景的选择:
- 对于 UX(设计师)和 DX(工程师)不需要选择不同的工具,都使用组件(Component)来定义用户体验。
- 不需要选择 SSR(服务端渲染)还是CSR(客户端渲染),使用 Server Component 和 Suspense,应用可以在客户端和服务端丝滑般转换。
- 有一天,为了原生的体验,不需要为不同平台做单独开发,只需要使用 React,就能在各端带来顶级的体验。
很美好的愿景,包括 小程序、Flutter 实际上都围绕类似的目标在尝试。但笔者认为,在跨端技术演进的过程中,各个端为了保持自己的优势也在不断演化,这会是一个不断促进的过程,可能没有终点。
总结
把 Concurrent 进行到底、Suspense 进行到底,SSR 进行到底
这次大会几乎所有技术点都是围绕 Concurrent,这是从阻塞渲染到可中断渲染进化的彻底性的改变/重写。整个过程大概思路是从 DOM Rendering(Fiber、transition/deferredValue)、Data Fetching(Suspense)、SSR(Server Component、Streaming SSR)、跨端(React Native)所有这一切都变成 Concurrent。目前看来 DOM Rendering 已经重构完成,接下来的新版本重点会围绕 Suspense 取数层和 SSR。
相比以往,本次大会能看出 React 对 SSR 非常重视。我在2016年的项目中有引用过 SSR,当时还没有 Next.js 这类框架,当时为了做 SSR 对前端组件做了大量改造,还引入 Node 服务增加了很多运维成本,是一次失败的尝试。现在已经 2022,不知道 SSR 现在体验如何,不做评论。
对 Concurrent 的大力投入,看得出 React 对用户体验和性能极致的追求。
Rewrite、Refactor
GraphQL 使用 Rust 重写引擎性能提升了 5 倍,React Native 计划重写为 Synchronous,而且印象中 RN 每年讲都是在重写。每次重写既大胆又能找到恰当的理由,这也是一种技术文化吧。
重写重构的时间都是以年为单位的,React Concurren 从最初 Fiber 引入开始,在 2016 年开始构思 Fiber,花了2年重写 reconciler 并在2018年 React 16 发布,然后 16.6 版本发布 Suspense,这是挖了一个更大的坑,到现在只支持 Relay,接下来 Suspense 还有几年的路要走。
React 依旧是企业应用开发首选
经历过 Angular 1.x 到 2.0,Vue 2.0 到 3.0 升级的人会觉得 React 这些年来升级平滑的不可思议。平滑升级是 React 追求的目标,但 React 并没有为了平滑而不敢添加功能,实际上这几年在 Hooks,Concurrent 的引入上非常的大胆。能做到平滑,是因为 React Team 背后做了很多的工作,包括大会上介绍的 React Working Group 让社区深度参与 API 制定,Facebook 内部大量的 Dogfooding,以及内核 API 非常克制保持极简,还有一开始就选择函数式编程这个非常成熟的方向。
企业级的应用大多开始后就没有终点,逻辑复杂,维护者会几经转手。像我们瓴羊的几个平台级产品代码量已经/未来都会是几百万行。长期来看,技术的升级成本是最大的成本之一,因此 React 依旧是企业应用开发首选。