React
当前的稳定版本是18.2,发布时间是22年6月,在此之后就没有新的稳定版本发布。
直到今年2月15日,官方博客[1]才透露下一个稳定版本的计划。没错,他就是React19
。
为什么时隔1年多才公布下个稳定版本的计划?
为什么下个版本直接跳到了19?
18我都还没升呢,19就来了,是不是要学很多东西?
这篇文章会为你详细解答这些疑问。
从React16聊起
近年来React
最为人津津乐道的版本应该是16.8
,这个版本引入了Hooks
,为React
(乃至整个前端框架领域)注入了新的活力。
再之后的v17
没有新特性引入。既然没有新特性引入,为什么要发布一个大版本(从16到17)呢?
这是因为从「同步更新」升级到「并发更新」的React
,中间存在breaking change
。
这么大体量的框架,在升级时需要保证过程尽可能平顺。这除了是一种专业、负责的体现,更重要的,版本割裂会造成大量用户损失(参考当年ng1
升级到anuglar2
时)。
当升级到18后,React团队
发现 —— 真正升级到18,并大量使用并发特性(比如useTransition
)的开发者并不多。
更常见的场景是 —— 知名开源库集成并发特性,开发者再直接用这些库。
所以,React团队
转变策略,将迭代重心从「赋能开发者」转变为「赋能开源库」。那么,什么样的库受众最多呢?显然是框架。
所以,React
的重心逐渐变为 —— 赋能上层框架,开发者通过使用上层框架间接使用React
。
为什么我说React团队
转变了策略,而不是React团队
一开始的计划就是「赋能上层框架」呢?
如果一开始的计划就是「赋能上层框架」,React团队
就不会花大量精力在「版本的渐进升级上」 —— 反正开发者最终使用的会是上层框架(而不是React
),版本割裂上层框架会解决,根本不需要引导开发者升级React
。
策略改变造成的影响
策略转变造成的影响是深远且广泛的,这也是为什么18.2后一年多都没有新的稳定版本出现。
最基本的影响是 —— 特性的迭代流程变了。
React
诞生的初衷是为了解决Meta
内部复杂的前端应用,所以React
之前的特性迭代流程是:
-
新特性开发完成
-
新特性在
React
内部产品试用,并最终达到稳定状态 -
开源供广大开发者使用
但随着策略转变为「赋能上层框架」,势必需要与主流上层框架团队(主要是Next.js
)密切合作。
如果按照原来的迭代流程,上层框架团队属于Meta
之外的第三方团队,只能等新特性开源后才能使用,这种合作模式显然太低效了。
于是,React
团队提出了一个新的特性发布渠道 —— canary
,即:新特性开发完成后,可以先打一个canary版本的React
供外部试用,等特性稳定后再考虑将其加入稳定版本中。
可能有些存在于canary
中的特性永远不会出现在稳定版本的React
中,但不妨碍一些开源库锁死canary
版本的React
,进而使用这些特性。
那么,为什么时隔1年多才公布下个稳定版本的计划?主要有4个原因。
原因1:新特性主要服务于Next,没必要出现在稳定版本中
策略改变除了影响「特性的迭代流程」,还让React团队成员
陷入一个两难的境地 —— 我该优先服务上层框架还是Meta
?
我们可以发现,在之前的迭代流程中,一切都围绕Meta
自身需求展开。React团队成员
作为Meta
员工,这个迭代流程再自然不过。
但是,新的迭代流程需要密切与Next
团队合作,那么问题来了 —— 作为Meta
员工,新特性应该优先考虑Next
的需求还是Meta
的需求?
为了完成「赋能上层框架」的任务,显然应该更多考虑Next
的需求。我们能看到一些React团队成员
最终跳槽到Vercel
,进入Next
团队。
所以,在此期间产出的特性(比如server action
、useFormStatus
、useFormState
)更多是服务于Next
,而不是React
。
如果基于这些特性发布新的稳定版本,那不用Next
的开发者用不到这些特性,用Next
的开发者依赖的是canary React
,所以此时升级稳定版本是没意义的。
原因2:新特性必须满足各种场景,交付难度大
Next
是web框架
,围绕他创造的新React
特性只用考虑web
这一场景。
但React
自身的定位是宿主环境无关的UI库
,还有大量开发者在非web
的环境使用React
(比如React Native
),所以这些特性要出现在稳定版本的React
中,必须保证他能适配所有环境。
举个例子,Server Actions
这一特性,用于「简化客户端与服务器数据发送的流程」,当前主要应用于Next
的App Router
模式中。
比如下面代码中的MyForm
组件,当表单提交后,serverAction
函数的逻辑会在服务端执行,这样就能方便的进行IO
操作(比如操作数据库):
// 服务端代码
async function serverAction(event) {
'use server'
// 在这里处理服务端逻辑,比如数据库操作读写等
}
function MyForm() {
return (
<form action={serverAction}>
<input name="query" />
<button type="submit">Search</button>
</form>
);
}
App Router
的场景主要是RSC
(React Server Component),除了RSC
外,SSR
场景下是不是也有表单?不使用服务端相关功能,单纯使用React
进行客户端渲染,是不是也有表单的场景?
所以,Server Actions
特性后来改名为Actions
,因为不止Server
场景,其他场景也要支持Actions
。
比如下面代码中,在客户端渲染的场景使用Actions
特性:
// 前端代码
const search = async (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const query = formData.get('query');
// 使用 fetch 或其他方式发送数据
const response = await fetch('/search', /*省略*/);
// ...处理响应
};
function MyForm() {
return (
<form action={search}>
<input name="query" />
<button type="submit">Search</button>
</form>
);
}
你以为这就完了?还早。form
组件支持Actions
,那开发者自定义的组件能不能支持Actions
这种「前、后端交互模式」?
比如下面的Calendar
组件,之前通过onSelect
事件响应交互:
<Calendar onSelect={eventHandler}>
以后能不能用Actions
的模式响应交互:
<Calendar selectAction={action}>
如何将平平无奇的交互变成Actions交互
呢?React团队
给出的答案是 —— 用useTransition
包裹。所以,这后面又涉及到useTransition
功能的修改。
Actions
只是一个例子,可以发现,虽然新特性是以web
为始,但为了出现在稳定版本中,需要以「覆盖全场景」为终,自然提高了交付难度。
原因3:老特性需要兼容的场景越来越多,工作量很大
新特性越来越多,老特性为了兼容这些新特性也必须作出修改,这需要大量的时间开发、测试。
举两个例子,Suspense
在v16.6就引入了,它「允许组件“等待”某些内容变得可用,并在此期间显示一个加载指示器(或其他后备内容)」。
Suspense
最初只支持懒加载组件(React.lazy
)这一场景。随着React
新特性不断涌现,Suspense
又相继兼容了如下场景:
-
Actions
提交后的等待场景 -
并发更新的等待场景
-
Selective Hydration
的加载场景 -
RSC
流式传输的等待场景 -
任何
data fetching
场景
为了兼容这些场景,Suspense
的代码量已经非常恐怖了,但开发者对此是无感知的。
再举个和Suspense
、useEffect
这两个特性相关的例子。
Suspense
为什么能在「中间状态」与「完成状态」之间切换?是因为在Suspense
的源码中,他的内部存在一个Offscreen
组件,用于完成两颗子Fiber树
的切换。
React团队
希望将Offscreen
组件抽离成一个单独的新特性(新名字叫Activity
组件),起到类似Vue
中Keep-Alive
组件的作用。
Activity
组件既然能让组件显/隐,那势必会影响组件的useEffect
的触发时机。毕竟,如果一个组件隐藏了,但他的useEffect create
函数触发了,会是一件很奇怪的事情。
所以,为了落地Activity
组件,useEffect
的触发逻辑又会变得更复杂。
原因4:特性必须形成体系才能交付
虽然这一年React团队
开发了很多特性,但很多特性无法单独交付,必须构成一个体系后再统一交付。
比如刚才提到的useFormStatus
、useFormState
是服务于Actions
特性的,Actions
又是由Server Actions
演化而来的,Server Actions
又是RSC
(React服务端组件)体系下的特性。
单独将useFormStatus
发布在稳定版本中是没意义的,他属于RSC
体系下的一环。
所以,即使新特性已经准备就绪,他所在的体系还没准备好的话,那体系下的所有特性都不能在稳定版本中交付。
总结
为什么时隔1年多才公布下个稳定版本的计划?主要是4个原因:
-
新特性主要服务于Next,没必要出现在稳定版本中
-
新特性必须满足各种场景,交付难度大
-
老特性需要兼容的场景越来越多,工作量很大
-
特性必须形成体系才能交付
那为什么下个稳定版本不是v18.x而是v19呢?这是因为部分新特性(主要是Asset Loading
、Document Metadata
这两类特性)对于一些应用会产生breaking change
,所以需要发一个大版本。
从上述4个原因中的第四点可以知道,既然有v19的消息,势必是因为「已经有成体系的新特性可以交付」,那是不是意味着要学很多东西呢?
这一点倒不用担心,如果你不用Next
,那你大概率不会接触到RSC
,既然不会接触RSC
,那么RSC
体系下的新特性你都不会用到。
v19对你最大的影响可能就是新特性对老API的影响了,比如:
-
useContext
变为use(promise)
-
Activity
组件使useEffect
的触发时机更复杂(应该不会在v19的第一个版本中)
这些的学习成本都不大。
关于v19的进一步消息,会在今年5月15~16的React Conf[2]公布。
参考资料
[1] 官方博客:https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024
[2] React Conf:https://conf.react.dev/