react18 来了,我 get 到...
大家好!
本文主要是关于即将发布的 react 18 的新特性。那么 react18 带来了什么呢?
详情可以关注 github React 18 工作组仓库
1. automatic batching:自动批处理。
batching 批处理,说的是,可以将回调函数中多个 setState 事件合并为一次渲染,因此是异步的。
解决的问题是多次同值、不同值 setState, 期望最后显示的是最后一次 setState 的结果,减少渲染。
const Index = () => {
const [name, setName] = useState('')
const [age, setAge] = useState(0)
const change = () => {
setName('a')
setAge(1)
// 仅触发一次渲染,批处理,2次setState合并为一次渲染
// 需需要立即重渲染,需要手动调用
// ReactDOM.flushSync(() => {
// setName('a') // 立即执行渲染
// setAge(1) // 立即执行渲染
// // 不会合并处理,即没有批处理,触发2次
// });
}
console.log(1) // 只打印一次
return (
<div>
<p>name: {name}</p>
<p>age: {age}</p>
<button onClick={change}>更改</button>
</div>
)
}
但是 react 18 之前,在 promise、timeout 或者 event 回调中调用多次 setState,由于丢失了上下文,无法做合并处理,所以每次 setState 调用都会立即触发一次重渲染:
const Index = () => {
const [name, setName] = useState('')
const [age, setAge] = useState(0)
const change = () => {
setTimeout(() => {
setName('a') // 立即执行渲染
setAge(1) // 立即执行渲染
// 不会合并处理,即没有批处理,触发2次
// 若需要批处理,需要手动调用
// ReactDom.unstable_batchedUpdates(() => {
// setName('a')
// setAge(1)
// // 合并处理
// })
// 并且将 ReactDOM.render 替换为 ReactDOM.createRoot 调用方式
// 旧 ReactDOM.render(<App tab="home" />, container);
// 新 ReactDOM.createRoot(container).render(<App tab="home" />)
}, 0);
}
console.log(1) // 打印2次
return (
<div>
<p>name: {name}</p>
<p>age: {age}</p>
<button onClick={change}>更改</button>
</div>
)
}
react18,在 promise、timeout 或者 event 回调中调用多次 setState,会合并为一次渲染。提升渲染性能。
v18实现「自动批处理」的关键在于两点:
- 增加调度的流程
- 不以全局变量 executionContext 为批处理依据,而是以更新的「优先级」为依据
参考:
2. concurrent apis:全新的并发 api。比如:startTransition
Concurrent:并发,采用可中断的遍历方式更新 Fiber Reconciler。是渐进升级策略的产物。
不同更新触发的视图变化是有轻重缓急的,让高优更新对应的视图变化先渲染,那么就能在设备性能不变的情况下,让用户更快看到他们想看到的UI。
案例:用户操作滑块,然后响应树的变化。滑块响应是高优先级的,而树的变化可以认为是低优先级的。
未开启:可以看到滑块的拖动有卡顿

开启:可以看到滑块的拖动,非常的丝滑顺畅

代码实现,将设置更新树的 setState,放到 startTransition 中。而更新滑块的不变,认为是高优先级,优先响应。
2部分:
- 紧急响应:滑块。
- 过渡更新:根据滑块,呈现结果内容。
import { useTransition } from 'react';
const [isPending, startTransition] = useTransition();
// 更改滑块触发
function changeTreeLean(event) {
const value = Number(event.target.value);
setTreeLeanInput(value); // 更新滑块
// 是否开启startTransition
if (enableStartTransition) {
startTransition(() => {
setTreeLean(value); // 这个变慢,根据滑块,呈现结果内容。
});
// react18之前,想要有类似功能。变体,setTimeout,防抖节流
// setTimeout(() => {
// setTreeLean(value)
// }, 0)
} else {
setTreeLean(value);
}
}
// 过渡期间可以这么处理
{isPending ? <Spinner /> : <Con>}
比 setTimeout 更好,能有状态 isPending,且更早更快的呈现更新到界面上(微任务里处理)。而且 setTimeout 是不可中断的,而 startTransition 是可中断的,不会影响页面交互响应。
依赖于React底层实现的优先级调度模型,被 startTransition 包含的 setState 的优先级会被设置为低优先级的过渡更新。
参考:
- 真实世界示例:为慢速渲染添加 startTransition
- 新功能:startTransition
- React 18不再依赖Concurrent Mode开启并发了
- 给女朋友讲React18新特性:startTransition
- A better React 18 startTransition demo
3. suspense:更好的 suspense。更好的支持在 ssr 和 异步数据 场景下使用 suspense。
1. ssr 下支持,可参考:React18 中的新 Suspense SSR 架构
2.透明的异步数据处理(未来18.x支持)
和写同步逻辑代码一样,写异步代码逻辑。大大的简化了代码逻辑的书写。把代数效应应用到极致了,把异步的副作用剥离了。
代数效应是函数式编程中的一个概念,用于将副作用从函数调用中分离。
场景案例:demo,显示畅销书排行榜。

其中,名称和日期是一个接口获取,而下面的列表是另一个接口获取。
从图中,可以明显感到 with suspense 的效果更丝滑,用户体验更好。而代码也非常简洁。部分代码如下:
```js
// 接口部分
import { fetch } from "react-fetch"
export function fetchBookLists() {
const res = fetch(`
https://api.nytimes.com/svc/books/v3/lists/names.json?api-key=${API_KEY}`)
const json = res.json()
if (json.status === "OK") {
return json.results
} else {
console.log(json)
throw new Error("Loading failed, likely rate limit")
}
}
// 组件部分
// 没有处理 loading 状态等的异步处理,和同步已经完全一致的代码书写
const Content = () => {
const list = fetchBookLists()[0]
return (
<>
<h4>From {list.display_name}</h4>
<Paragraph sx={{ mt: -3 }}>
Published on {list.newest_published_date}
</Paragraph>
<BookList list={list} />
</>
)
}
export const BestSellers = () => {
return (
<Suspense fallback={<Spinner />}>
{/* loading must happen inside a <Suspense> */}
<Content />
</Suspense>
)
}
```
而在 react18 之前,你得这么写:
```js
// 接口部分
import { fetch } from "react-fetch"
export async function fetchBookLists() {
const res = await fetch(`
https://api.nytimes.com/svc/books/v3/lists/names.json?api-key=${API_KEY}`)
const json = await res.json()
if (json.status === "OK") {
return json.results
} else {
console.log(json)
throw new Error("Loading failed, likely rate limit")
}
}
// 组件部分,按照异步的逻辑写,写loading,对异步结果的处理等
function useNYTBestSellerLists() {
// poor man's useQuery implementation
const [isLoading, setIsLoading] = useState(false)
const [lists, setLists] = useState(null)
useEffect(() => {
setIsLoading(true)
fetchBookLists()
.then((lists) => {
setLists(lists)
setIsLoading(false)
})
.catch(() => setIsLoading(false))
}, [])
return { isLoading, lists }
}
export const BestSellers = () => {
const { isLoading, lists } = useNYTBestSellerLists();
if (isLoading) {
return <Spinner />;
}
if (!lists) {
return "not loading or error";
}
const list = lists[0];
return (
<>
<h4>From {list.display_name}</h4>
<Paragraph sx={{ mt: -3 }}>
Published on {list.newest_published_date}
</Paragraph>
<BookList list={list} />
</>
);
}
```
参考:
3.优化 suspense 的行为表现。
场景举例:
js <Suspense fallback={<h3>loading...</h3>}> <LazyCpn /> // 为 React.lazy 包裹的异步加载组件 <Sibling /> // 普通组件 </Suspense>
由于 Suspense 会等待子孙组件中的异步请求完毕后再渲染,所以当代码运行时页面首先会渲染 fallback:loading。而在loading这个过程中,页面表现是一致的,但是背后的行为是不一致的:
- react18 之前:即在 Legacy Suspense 中,Sibling 组件会立即安装到 DOM 并触发其效果/生命周期。页面上隐藏。
- react18:即在 Concurrent Suspense 中,Sibling 组件没有挂载到 DOM。它的效果/生命周期也不会在 ComponentThatSuspends 解决之前触发。

react18,Sibling 不会执行,会等 suspense 包裹的组件都加载完才执行渲染

优化的是提交渲染的流程:
打断兄弟组件并阻止他们提交。等待提交 Suspense 边界内的所有内容- 挂起的组件及其所有兄弟组件 - 直到挂起的数据解决。然后在一个单一的、一致的批次中同时提交整个树渲染。
参考:
4. 其他
比如:新 Hook —— useId
解决问题:ssr 场景下,客户端、服务端生成的id不匹配!官方推出 Hook——useId解决,每个 id 代表该组件在组件树中的层级结构。
function Checkbox() {
// 生成唯一、稳定id
const id = useId();
return (
<>
<label htmlFor={id}>Do you like React?</label>
<input type="checkbox" name="react" id={id} />
</>
);
);
参考:为了生成唯一id,React18专门引入了新Hook:useId
最后
这几个重大的更新,目的都是较少渲染、根据优先级响应、提升性能、拥有更好的体验。非常值得期待。
想尝鲜的可安装 react18 beta 版(2021-11-16发布的)
# npm
npm install react@beta react-dom@beta
# yarn
yarn add react@beta react-dom@beta
react18 来了,我 get 到...的更多相关文章
- 构建基于React18的电子表格程序
背景 2022年3月29日,React正式发布18.0.0.本次升级内容包括开箱即用的改进,如自动批处理.新的API(如startTransition)和支持Suspense 的流式服务器端渲染.关于 ...
- React Suspense 尝鲜,处理前后端IO异步操作
简单介绍一下Suspense Suspense主要用来解决网络IO问题,它早在2018年的React 16.6.0版本中就已发布.它的相关用法有些已经比较成熟,有的相对不太稳定,甚至经历了重命名.删除 ...
随机推荐
- 菜鸡的Java笔记 第十二 - java 构造方法与匿名对象
1.点 构造方法的作用以及定义要求 匿名对象的使用 构造方法: 只要出现()的都表示方法 构造方法就是类构造对象时调用的方法,主要用来实例化对象.> ...
- 软件分享:网页监测及 IIS 重启工具 IISMonitor
本人以前编写过一款简单的工具软件 IISMonitor,这几天整理完善并补写了使用说明,分享出来,供大家免费使用.使用过程中,遇到什么问题或有什么建议,也可回帖留言,我尽力提供修改支持. 1.工具简介 ...
- [luogu5426]Balancing Inversions
由于交换是相邻交换,所以分为两类:1.左右区间内部交换,那么一定会让逆序对数量$\pm 1$,也就是说如果没有左右区间之间交换,那么答案就是$|ansL-ansR|$(ans表示逆序对数量)2.左右区 ...
- vue-ref指令
$refs是数组
- CF30E. Tricky and Clever Password
被你谷翻译诈骗了兄弟. 不过下次可以拿去诈骗其他人. 考虑枚举B,显然结论有B作为回文串越长越好,这个可以使用manacher,或者直接二分hash. 然后考虑翻转末尾串,然后记录其匹配到第 \(i\ ...
- 【CSP2019】【洛谷5657】格雷码
传送门:https://www.luogu.com.cn/problem/P5657 题意不再复述: 我们知道对于每个字符1 or 0: 只要考虑当前的k在2^n的前半段还是后半段就行 这里需要注意的 ...
- 【POJ1961 Period】【KMP】
题面 一个字符串的前缀是从第一个字符开始的连续若干个字符,例如"abaab"共有5个前缀,分别是a, ab, aba, abaa, abaab. 我们希望知道一个N位字符串S的前缀 ...
- [CF707 Div2, A ~ D]
(相信进这个博客的人,都已经看过题目了,不再赘述) 这把打小号打到了\(484\),\(rating + 636\) \(A\) 考虑进行模拟就行了,说白了这是一个英语阅读题 // code by D ...
- 【2020五校联考NOIP #8】狗
题面传送门 原题题号:Codeforces 883D 题意: 有 \(n\) 个位置,每个位置上要么有一条狗,要么有一根骨头,要么啥都没有. 现在你要给每个狗指定一个方向(朝左或朝右). 朝左的狗可以 ...
- 学习Java的第四天
一.今日收获 1.java完全手册的第一章 2. 1.6节了解了怎么样用记事本开发java程序 与用Eclipse开发 2.完成了对应例题 二.今日难题 1.一些用法容易与c++的混淆 2.语句还 ...