一种新的幕后机制,使 React 能够同时准备多个版本的 UI。您可以将并发视为一个实现细节——它的价值在于它的特性
并发涉及同时执行多个状态更新,这可以说是 React 18 中最重要的特性。除了并发之外,React 18 还引入了两个新的钩子,称为 useTransition() 和 useDeferredValue() 钩子。
它们都有助于降低状态更新的优先级
concurrent Mode 渲染并发模式在react 18 中才可以使用,要先开启并发模式,也就是需要通过 createRoot 创建 Root 。(脚手架会自动创建)
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( );
useDeferredValue 和useTransition这两个钩子可以让我们延迟渲染不紧急的部分,类似于防抖但没有固定的延迟时间,延迟的渲染会在紧急的部分先出现在浏览器屏幕以后才开始,并且可中断,不会阻塞用户输入。
简单理解就是如果说某些渲染比较消耗性能,比如存在实时计算和反馈,我们可以使用这个Hook降低其计算的优先级,使得避免整个应用变得卡顿。较多的场景可能就在于用户反馈输入上。比如百度的输入框,用户在输入的时候,页面会发生变化,但是输入框输入并不卡顿
接收一个要变化的参数,该参数会被打上标记低优先级更新, 返回一个延迟响应的状态。我们在使用的时候,想要对useState里面的数据延迟更新,则可以把输入传到useDeferredValue中,然后使用它的返回值
const deferredValue = useDeferredValue(value)
当用户输入的时候,大量的dom的更新会造成输入框的卡顿,使用useDeferredValue来优化一下
import { useTransition } from "react";
import { useState, useDeferredValue } from "react";
export default function App() {
const [value, setValue] = useState("");
const deferredValue = useDeferredValue(value)
const handleChange = (e) => {
setValue(e.target.value);
};
return (
{Array(100)
.fill("a")
.map((item, index) => (
{deferredValue}
))}
{Array(20000)
.fill("a")
.map((item, index) => (
{deferredValue}
))}
);
}
useTransition() 告诉 React 一些状态更新具有较低的优先级。当我们调用 useTransition() 时,我们得到一个包含两个元素的数组:
isPending 布尔值,它指示低优先级状态更新是否仍处于挂起状态,
startTransition() 函数, 接收一个回调函数,优先级较低的任务可以放到函数里面
const [isPending, startTransition] = useTransition();
当用户输入的时候,大量的dom的更新会造成输入框的卡顿,使用useTransition来优化一下
import { useState, useTransition } from "react";
export default function App() {
const [value, setValue] = useState("");
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
startTransition(() => {
setValue(e.target.value);
});
};
return (
{isPending && "等待中"}
{Array(100)
.fill("a")
.map((item, index) => (
{value}
))}
{Array(20000)
.fill("a")
.map((item) => (
{value}
))}
);
}
当使用大屏幕的时候,这里的大屏幕并不是单纯指的是尺寸,而是一种数据量大,DOM 元素节点多的场景
useDeferredValue 本质上和内部实现与 useTransition 一样都是把任务标记成了过渡更新任务。
useTransition 是把 startTransition 内部的更新任务变成了过渡任务transtion,而 useDeferredValue 是把原值通过过渡任务得到新的值,这个值作为延时状态。 也就是说一个是处理一段逻辑,另一个是生产一个新的状态。
useDeferredValue 还有一个不同点就是这个任务,本质上在 useEffect 内部执行,而 useEffect 内部逻辑是异步执行的 ,所以它一定程度上更滞后于 useTransition。可以理解成useDeferredValue = useEffect + transtion
1、节流防抖本质是 setTimeout ,只不过控制了执行的频率,原理就是让 render 次数减少了。而 transitions 和它相比,并没有减少渲染的次数。
2、节流和防抖需要有效掌握延迟时间,如果时间过长,那么给人一种渲染滞后的感觉,如果时间过短,那么就类似于 setTimeout(fn,0) 还会造成前面的问题。而 startTransition 就不需要考虑这么多
npm install ahooks
import React, { useState, useEffect, useDeferredValue } from "react";
import { useDebounce } from "ahooks";
const List = (props) => {
const [list, setList] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => {
setCount((count) => count + 1);
setList([
{ name: props.text },
{ name: props.text },
{ name: props.text },
{ name: props.text },
]);
}, [props.text]);
return [
{"我被触发了" + count + "次"}
,
{list.map((item, index) => (
-
Hello:{item.name} value:{item.value}
))}
,
];
};
export default function App() {
const [text, setText] = useState("");
const deferredText = useDeferredValue(text);
/**
* 第一个参数要节流的state
* 也就是放useState里面拿到值
* 当这个值频繁变化的时候,只会触发最后一次
* 第二个参数是一个对象
* 里面可以传一个wait 等待时间
*/
console.log(text);
const debouncedValue = useDebounce(text, { wait: 1000 });
const handleChange = (e) => {
setText(e.target.value);
};
return (
DeferredValue
防抖
);
}
1、startTransition 的处理逻辑和 setTimeout 有一个很重要的区别,setTimeout 是异步延时执行,而 startTransition 的回调函数是同步执行的。在 startTransition 之中任何更新,都会被react打上一个标记,React 在更新的时候会通过这个标记来判断是否完成本次更新。所以startTransition 可以理解成比 setTimeout 更早的更新。
2、在 conCurrent mode 下,startTransition 是可以中断渲染的 ,所以它不会让页面卡顿,React 让这些任务,在浏览器空闲时间执行,所以上述输入 input 内容时,startTransition 会优先处理 input 值的更新,而之后才是列表的渲染。setTimeout则是只要到时间,就会执行。