前言
书接上回,React 17 和 React 18 中的setState 都已摸清到底是怎么回事,但是里面的代码全部是基于class 组件的形式
但是现在 React 推荐的都是 hooks 写法,那 hooks 和 class 组件在处理 setState 上有什么区别吗?
话不多说,咱们直接上代码看下结果:
大家好,我是【前端探险家克鲁】。微信公众号、知乎、掘金、CSDN同名,欢迎查看我的个人简介,一起学习提升。
将面试题改造成hooks形式
jsx
import { useState } from 'react';
import './App.css';
function App() {
const [count, setCount] = useState(0);
const handle = () => {
setCount(1);
console.log('count: ', count);
setCount(2);
console.log('count: ', count);
setTimeout(() => {
setCount(3);
console.log('count: ', count);
setCount(4);
console.log('count: ', count);
}, 0);
};
return (
<>
<div className='App'>
<button onClick={handle}>count is {count}</button>
</div>
</>
);
}
export default App;
看下在 react 17和 React 18下的运行结果:
怎么回事,都是 0,而且两个版本下表现一致,看起来像是都在批处理中
真的是这么回事吗?为了能更清楚的看到 React 的渲染行为,修改下上边的代码,在每次渲染都都打印下当前的 count
值,添加如下代码:
jsx
useEffect(() => {
console.log('render: 此时的count: ', count);
});
再次查看输出结果:
分析下:
- 在 React 17 下,render 打印了 3 次,说明组件重新渲染了 3 次,回顾下当时讲解 React 17 的
setState
时,批处理重新渲染一次,setTimeout
中是同步渲染,重新渲染两次,能对上 - 在来看下 React 18 下,render 打印了 2 次,说明组件重新渲染了 2 次,在对比下前面讲解的 React18 的自动批处理功能,
setTimeput
外部批处理一次,内部批处理一次,重新渲染两次,也能对上 - 那为什么这期间打印的 count 都是 0 呢?
解惑
闭包
首先需要先知道一个 js 的特性:闭包,下面是 mdn 中关于闭包的解释
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
简单说就是:内部函数可以访问外部函数的变量
组件 App
是一个函数,handle
是 App
内的函数,这也就形成了一个闭包,所以 handle
函数才能访问到 count
变量的值。
结论
因为在react
中state
的更新都遵从一个原则:**state**
指向的内容是不可变的。
state
改变时不是改变当前内容而是指向了新的内容对象
所以在上面setCount
改变count
时,count
指向了新的内容对象
又因为在函数组件中,handle
函数与组件App
形成了闭包关系
这就导致handle
函数一直持有count
的引用而无法发生变化,所以无论延时多长时间,最终打印的count
值一直是旧值,也就是 0