react 异步渲染如何理解
异步渲染(Async Rendering)是指 React 在更新组件时,能够以异步的方式执行渲染操作,从而提高性能和用户体验。在异步渲染中,React 会将渲染工作分割成多个小任务,这些小任务可以在多帧中逐渐完成,避免了长时间的主线程阻塞,使得页面更加流畅响应。
在 React 中,异步渲染是由 React 的调度器(Scheduler)来管理的。React 16.8 版本引入了 useState
、useEffect
等 Hook,以及 React 17 版本引入的并发模式(Concurrent Mode)都是基于异步渲染的概念。
以下是一个简单的示例,演示了 React 的异步渲染特性。由于 React 使用异步渲染,即使计算操作很耗时,也不会阻塞主线程。
import React, { useState, useEffect } from 'react';
function AsyncRenderingExample() {
const [count, setCount] = useState(0);
const [result, setResult] = useState(null);
useEffect(() => {
// 模拟一个耗时的计算操作
const calculateResult = () => {
let sum = 0;
for (let i = 1; i <= count; i++) {
sum += i;
}
return sum;
};
// 使用定时器模拟异步操作
const timer = setTimeout(() => {
const calculatedResult = calculateResult();
setResult(calculatedResult);
}, 1000);
// 清除定时器以防止内存泄漏
return () => clearTimeout(timer);
}, [count]);
return (
<div>
<h1>Async Rendering Example</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
{result !== null && <p>Result of the calculation: {result}</p>}
</div>
);
}
export default AsyncRenderingExample;
问题 1:以上代码,useEffect
最后清除定时器,定时器的内容为什么还会执行?
在 React 中,useEffect
是一个副作用函数,它可以执行异步操作、订阅数据、手动修改 DOM 等。当使用 useEffect
时,返回的函数(清除函数)会在组件销毁时执行,用来清理副作用。在你的代码中,useEffect
返回的清除函数清除了定时器。
你的疑问可能出现在定时器执行的时机。在 React 中,由于函数组件在每次渲染时都会重新执行,useEffect
中的函数也会重新执行。在每次组件重新渲染时,新的定时器会被创建,旧的定时器会被清除。所以,每次点击 "Increment" 按钮,都会清除上一次的定时器,然后创建一个新的定时器。
这样的行为导致了在计算结果返回之前,可能会出现多个定时器同时存在的情况。当定时器的回调函数执行时,它会引用当前渲染周期内的 count
变量。这就是为什么你会看到,即使定时器被清除,它仍然可以引用到之前的 count
值。
为了确保在每个渲染周期只有一个定时器存在,你可以使用 useRef
来存储定时器的引用,并在 useEffect
中清除旧的定时器。这样可以确保在下一次渲染周期时,只有一个定时器在执行。
以下是修改后的代码示例:
import React, { useState, useEffect, useRef } from 'react';
function AsyncRenderingExample() {
const [count, setCount] = useState(0);
const [result, setResult] = useState(null);
const timerRef = useRef(null);
useEffect(() => {
// 清除旧的定时器
if (timerRef.current) {
clearTimeout(timerRef.current);
}
// 模拟一个耗时的计算操作
const calculateResult = () => {
let sum = 0;
for (let i = 1; i <= count; i++) {
sum += i;
}
return sum;
};
// 使用定时器模拟异步操作
timerRef.current = setTimeout(() => {
const calculatedResult = calculateResult();
setResult(calculatedResult);
}, 1000);
// 清除定时器以防止内存泄漏
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, [count]);
return (
<div>
<h1>Async Rendering Example</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
{result !== null && <p>Result of the calculation: {result}</p>}
</div>
);
}
export default AsyncRenderingExample;
在这个修改后的代码中,使用 useRef
创建了一个 timerRef
,用来存储定时器的引用。每次 useEffect
执行时,它会清除旧的定时器,并将新的定时器引用保存在 timerRef.current
中。这样可以确保在每个渲染周期内只有一个定时器存在。
问题 2:当 useEffect 执行,组件重新渲染,为什么 useState 的值不会重置为初始值?
在 React 函数组件中,每次组件重新渲染时,useState
返回的状态值不会重置为初始值。这是因为 React 通过使用闭包来保留useState
返回的当前状态值。当组件重新渲染时,React 会记住当前的闭包,因此状态值在不同渲染周期之间得以保持。