原文:Everything You Need to Know About the React 18 RC
关于React 18 RC
the React 18 RC (Release Candidate) was released March 8, 2022
React is Design Focused
Concurrent Rendering
如果你要选择一个单词来总结React 18 的发布,那么一定是Concurrent。Concurrency是隐藏在背后的能力,并且给很多的新特性赋能了在即将到来的这次更新中,例如Suspense和新的API: startTransition()和useDeferredValue()。
在高水平上,Concurrency从基础上讲意味着任务能够重叠。而不是一个状态的更新要完整的完成再系统要切换到下一个任务之前,Concurrency使得我们能够在多个任务之间来回切换。需要注意的是这并不意味着这些任务是同时发生 - 相反,一个任务可以被暂停当有其他的、更紧急的任务时。所以,一旦更紧急的任务完成了,那么就跳回到次紧急的任务,并从紧急任务带来更新后的信息。
React18提供给我们的是,是可以使用和操作并行流的工具。相比于以前,开发者具有对渲染优先级和顺序更多的控制。
Suspense
React中最优秀的事情就是易于人类阅读的代码。这是相对容易的对于开发者打开一个文件阅读代码,从上到下,可以很快的理解在这个组件里发生了什么。
然而,当我们去抽取和处理数据时,这些热容易的事情就没有了。开发者这时候就会转向一些数据抽取的库,例如Apollo 和 React Query,他们提供了一些API和hooks可以处理这些复杂度。
尽管有这些解决方案,然而,仍然有一些问题需要去处理 - 主要的是,数据和加载状态在本质上是连接在一起的。在这之前,我们不得不明确指出一些类别的加载状态,然后基于此,写对应的JSX有条件的渲染。这意味着我们的UI元素和特定的数据的加载是仅仅绑定在一起的。
const [loading, setLoading] = useState(true);if myData != null {setLoading(true);}<>{ !loading &&<MyComponent />}{ loading &&<Loading />}<>
Suspense solves that problem允许我们设置fallback为那些还没有准备好渲染的UI元素。
<Suspense fallback={<Loading/>}><MyComponent myData={myData}/></Suspense>
有趣的是,它受到设计原则的启发 — 确切地说,框架布局的概念,UI元素总是在确切的位置,当内容准备好了占据确切的位置。这个理论帮助开发者通过写代码更精确的组合实际的设计,缩小原型和运行应用程序的差距。
这个理论使得重新修改页面的UI更加的容易 — 是分开装载还是一起装载,什么时间,和在哪里 - 因为我们仅仅是添加<Suspense>组件(甚至和其他的<Suspense>组件嵌套在一起)或者把其他的元素移除或者移入已有的<Suspense>组件来快速的重新排列页面的布局。因为<Suspense>组件本身不会和特定的数据绑定(就像我们以前那样),他把UI代码从功能代码中分离出来从而真正的重视设计体验。
我们没有局限于仅仅为数据使用<Suspense>,也可以应用于 streaming server rendering。
Streaming Server Rendering
服务端渲染是一种技术,输出React组件到HTML,和把它发送到客户端,在JS准备好之前,以便于用户不用一直盯着空白的页面。在React18之前,这个发生或者是一种全有的方式或者是一种全无的方式 — 当所有的组件准备好了,页面会更新并且用户开始和应用程序交互。这意味着如果你仅有一个很慢的组件,例如一个复杂的数据表格,那么这个组件将会是一个性能瓶颈。
图片来自React 18 for App Developers。
现在,我们有了Suspense。像我们之前谈论的,我们可以把单个加载慢的组件包裹在<Suspense>标签里,并且告诉组件延迟加载,并首先聚焦于向下转发其他的、更小的组件。你也可以,像之前提到的,设置一个fallback显示一个加载动画。
图片来自React 18 for App Developers。
这使得用户一旦页面可用,就可以看到内容,基于组件的基础上,而不需要等到所有的东西都准备好,然后一次性拿到所有。你可以先展示页面的初始框架,然后慢慢的显示剩余的。
Automatic Batching
React18 中另一个伟大的更新是automatic batching。首先先说明下 batching是什么,然后再深入React 18 中的概念。
之前,批量更新发生在一个处理函数里有多个状态更新;在这种情况下,React会重新渲染一次在函数的末尾 - 并不是在每一次状态的改变。然而,这不会发生在事件处理器的外面 - 如果在一个fetch的调用里有多个状态更新,例如,每次更新都会触发一次重新渲染。
fetch('http://example.com/data.json').then(() => {setIsLoading(false);setData(data);setError(null);});// Previously this code would cause 3 different re-renders, once for each state update.// Now, these three updates will be batched together into 1 re-render.
现在,更新时自动批量,而不管它们是被什么包裹。这会使你的代码变得更有效率,阻止一些不必要的渲染。然而,如果有必要的话,你可以选择退出在一些特定的情况下,如果你想要重新渲染的话。
New APIs
startTransition()
当我们使用startTransition API的时候,就是把一些不紧急的动作当做 ‘transition’,告诉Reat让一些更紧急的在渲染时间线里优先执行。
从UX的角度看这是一个令人惊叹的更新。通过在startTransition包裹这些慢的、不紧急的更新,我们可以使React延后处理当没有忙于处理更重要的事情时。
这意味着transition能够被更紧急的更新打断,React会扔掉那些没有完成的、不合时宜的渲染工作然后跳转到新的事情上。这也意味着我们再也不会处于那种境地,为渲染过时的和不准确的数据而浪费时间。更糟糕的是,用户看到的信息不再正确。
onChange = (e) => {const value = e.target.value;startTransition(() => {nonUrgentAction(value);});};
useTransition()
由于你的页面不再被锁定因为等待那些漫长的过程,你的用户不会意识到一些东西一直在加载中。
由于这和原因,也同样建议 isPending这个值作为useTransition()这个hook的一部分,随着React18一起发布。这个hook返回startTransition函数,同样的isPending这个值也会被设置为true当transition正在渲染时。这种情况下,你可以很快的检查isPending的值,来决定是否需要调整UI来标明 更新还没有准备好 — 例如,禁用一个button。
const [isPending, startTransition] = useTransition();<Button className={isPending ? 'disabled' : 'active'} />
useDeferredValue()
新的useDeferredValue()API允许你选取特定UI的一部分并且有意的延迟更新他们,以至于他们不会
减慢页面的其他部分。有两个有点1)控制渲染的顺序,2)显示之前的值能力而不是仅仅显示一个加载动画或者灰色的盒子。
就像前面提到的,这是一个面向设计的更新。没有页面充满加载动画更糟糕的了,有时候有一些老数据可能比一点数据也没有要好。这使得我们的组件不会感到一直在加载,即便他们真的是在背后运行。对于用户来说,他们只是在更新。
这里有一个例子来是说明是怎么工作的:假设我们从从数据源请求数据value,然后定期更新,但是有很多的内容,通常我们需要花很多的时间来加载。现在通过useDeferredValue我们可以背后请求新的数据,通过让我们的组件使用老的value内容来创建快速的、平滑的更新的错觉,长达4000ms的时间。
const deferredValue = useDeferredValue(value, { timeoutMs: 4000 });return (<div><MyComponent value={deferredValue} /></div>);
Say Goodbye to ReactDOM.render
有一件事情需要注意的是,ReactDOM.render语法的终结,之前用于应用程序中处理DOM。被ReactDOM.createRoot代替,你的代码仍然会工作,但是你会在**console**里得到一个错误,你不能够在你新版发布里应用新特性。
// The old way:ReactDOM.render(<App />,document.getElementById('root'));// The new way:ReactDOM.createRoot(document.getElementById('root'));Root.render(<App/>);
No Breaking Changes!
如果你留神React过去的更新,你可能听说讨论“Concurrent Mode”这个单词。这已经过时了,Concurrent Mode 不再是React18 中应用的策略。相反,你听过 ,Concurrent features。就像React团队说的那样,“There is no Concurrent Mode, only Concurrent features!”。
这意味着,从实际来讲,没有一个高级的flag需要去关注 - 应用并行渲染,你可以仅仅是添加一些并行特性在你需要的地方,具体问题具体分析,你不需要担心对你其他系统的影响。因为所有并行特性都是可选的,这意味着你不得不通过声明一个动作作为transition并通过setTransition来包裹,例如和很多东西被自动设置相比 - - 已有的代码不会被这些变更影响到。React18 默认的把所有的更新当成紧急的更新,除非你通过应用concurrent features 还有其他的处理。这意味着你可以升级或者有选择的应用这些新的特性在你的代码库里面。
Get Ready to Upgrade
How to Upgrade to the React 18 Release Candidate

