作者:梁瑞锋(晓玉)
React 重新渲染,指的是在类函数中,会重新执行 render 函数,类似 Flutter 中的 build 函数,函数组件中,会重新执行这个函数
React 组件在组件的状态 state 或者组件的属性 props 改变的时候,会重新渲染,条件简单,但是实际上稍不注意,会引起灾难性的重新渲染
为什么拿类组件先说,怎么说呢,更好理解?还有前几年比较流行的一些常见面试题
React中的setState什么时候是同步的,什么时候是异步的
ReactsetState怎么获取最新的state
以下代码的输出值是什么,页面展示是怎么变化的
test = () => {
// s1 = 1
const {
s1 } = this.state;
this.setState({
s1: s1 + 1});
this.setState({
s1: s1 + 1});
this.setState({
s1: s1 + 1});
console.log(s1)
};
render() {
return (
<div>
<button onClick={
this.test}>按钮</button>
<div>{
this.state.s1}</div>
</div>
);
}
看到这些类型的面试问题,熟悉
React事务机制的你一定能答出来,毕竟不难嘛,哈?你不知道React的事务机制?百度|谷歌|360|搜狗|必应 React 事务机制
React 合成事件在 React 组件触发的事件会被冒泡到 document(在 react v17 中是 react 挂载的节点,例如 document.querySelector(‘#app’)),然后 React 按照触发路径上收集事件回调,分发事件。
React 事件就没法触发了?确实是这样,没法冒泡了,React 都没法收集事件和分发事件了,注意这个冒泡不是 React 合成事件的冒泡。React ,就算是在合成捕获阶段触发的事件,依旧在原生冒泡事件触发之后reactEventCallback = () => {
// s1 s2 s3 都是 1
const {
s1, s2, s3 } = this.state;
this.setState({
s1: s1 + 1 });
this.setState({
s2: s2 + 1 });
this.setState({
s3: s3 + 1 });
console.log('after setState s1:', this.state.s1);
// 这里依旧输出 1, 页面展示 2,页面仅重新渲染一次
};
<button
onClick={
this.reactEventCallback}
onClickCapture={
this.reactEventCallbackCapture}
>
React Event
</button>
<div>
S1: {
s1} S2: {
s2} S3: {
s3}
</div>
setState定时器回调执行 setState 是同步的,可以在执行 setState 之后直接获取,最新的值,例如下面代码
timerCallback = () => {
setTimeout(() => {
// s1 s2 s3 都是 1
const {
s1, s2, s3 } = this.state;
this.setState({
s1: s1 + 1 });
console.log('after setState s1:', this.state.s1);
// 输出 2 页面渲染 3 次
this.setState({
s2: s2 + 1 });
this.setState({
s3: s3 + 1 });
});
};
setState异步函数回调执行 setState 是同步的,可以在执行 setState 之后直接获取,最新的值,例如下面代码
asyncCallback = () => {
Promise.resolve().then(() => {
// s1 s2 s3 都是 1
const {
s1, s2, s3 } = this.state;
this.setState({
s1: s1 + 1 });
console.log('after setState s1:', this.state.s1);
// 输出 2 页面渲染 3 次
this.setState({
s2: s2 + 1 });
this.setState({
s3: s3 + 1 });
});
};
原生事件同样不受 React 事务机制影响,所以 setState 表现也是同步的
componentDidMount() {
const btn1 = document.getElementById('native-event');
btn1?.addEventListener('click', this.nativeCallback);
}
nativeCallback = () => {
// s1 s2 s3 都是 1
const {
s1, s2, s3 } = this.state;
this.setState({
s1: s1 + 1 });
console.log('after setState s1:', this.state.s1);
// 输出 2 页面渲染 3 次
this.setState({
s2: s2 + 1 });
this.setState({
s3: s3 + 1 });
};
<button id="native-event">Native Event</button>
setState 修改不参与渲染的属性setState 调用就会引起就会组件重新渲染,即使这个状态没有参与页面渲染,所以,请不要把非渲染属性放 state 里面,即使放了 state,也请不要通过 setState 去修改这个状态,直接调用 this.state.xxx = xxx 就好,这种不参与渲染的属性,直接挂在 this 上就好,参考下图
// s1 s2 s3 为渲染的属性,s4 非渲染属性
state = {
s1: 1,
s2: 1,
s3: 1,
s4: 1,
};
s5 = 1;
changeNotUsedState = () => {
const {
s4 } = this.state;
this.setState({
s4: s4 + 1 });
// 页面会重新渲染
// 页面不会重新渲染
this.state.s4 = 2;
this.s5 = 2;
};
<div>
S1: {
s1} S2: {
s2} S3: {
s3}
</div>;
setState,页面会不会重新渲染几种情况,分别是:
setState,无参数setState,新 state 和老 state 完全一致,也就是同样的 statesameState = () => {
const {
s1 } = this.state;
this.setState({
s1 });
// 页面会重新渲染
};
noParams = () => {
this.setState({
});
// 页面会重新渲染
};
这两种情况,处理起来和普通的修改状态的 setState 一致,都会引起重新渲染的
为什么要提上面这些,仔细看,这里提到了很多次渲染的 3 次,比较契合我们日常写代码的,异步函数回调,毕竟在定时器回调或者给组件绑定原生事件(没事找事是吧?),挺少这么做的吧,但是异步回调就很多了,比如网络请求啥的,改变个 state 还是挺常见的,但是渲染多次,就是不行!不过利用 setState 实际上是传一个新对象合并机制,可以把变化的属性合并在新的对象里面,一次性提交全部变更,就不用调用多次 setState 了
asyncCallbackMerge = () => {
Promise.resolve().then(<