likes
comments
collection
share

React函数组件中的闭包陷阱

作者站长头像
站长
· 阅读数 18

在React中有 Class & Function 两种类型的组件,在使用上会有一些区别,典型的就是函数组件是有闭包状态,没有上下文的this,而Class 类型的组件具有指向上下文的this。

1、问题场景

看一个场景。在一个页面中,有两个按钮,一个按钮点击会自增count变量的值,另一个alert按钮点击之后会在3s之后弹出count的值。 React函数组件中的闭包陷阱

正常的话使用函数组件来实现这个功能,先点击若干次自增count的按钮,再点击alert按钮(此时此刻count的有个值val1),然后接着继续点击自增按钮,3s之后alert函数起作用了(这时候count的值为val2)出来的count的值是val还是val2呢?如果相同的功能使用Class组件会有什么不一样么。?

我们直接看下不同实现下的相关代码和效果。

2、使用函数组件

import React, { useState } from "react";

const Demo1 = () => {
  let [count, setCount] = useState(0);

  const addCount = () => {
    setCount(count + 1);
  };

  const alertCount = () => {
    setTimeout(() => {
      alert(count);
    }, 3000);
  };

  return (
    <div>
      <button onClick={addCount}>add Count</button>
      <button onClick={alertCount}>alertCount</button>
      <div>count: {count}</div>
    </div>
  );
};

export default Demo1;

很简答的一个小功能,看下操作之后的效果 React函数组件中的闭包陷阱 可以看到,点击alert 按钮的那一瞬间count的值为5,然后接着点击add count 按钮使count自增,3s之后count的值为16,弹出来的为5。再来看一下使用Class组件实现相同的功能:

3、class实现

import React from "react";

class Demo3 extends React.Component {
  state = {
    count: 0
  };

  addCount = () => {
    this.setState({
      count: this.state.count + 1
    });
  };

  alertCount = () => {
    // 记录点击alert按钮那一时刻count的值
    const recordCount = this.state.count;
    setTimeout(() => {
      // 返回的始终是 this对象上的状态
      alert(
       `count: ${this.state.count}`
      );
    }, 3000);
  };

  render() {
    return (
      <div>
        <button onClick={this.addCount}>add Count</button>
        <button onClick={this.alertCount}>alertCount</button>
        <div>count: {this.state.count}</div>
      </div>
    );
  }
}

export default Demo3;

看一下效果如何

React函数组件中的闭包陷阱 可以看到相同的操作,但在Class组件中alert出来的是15 而不是5。

4、原因

这是因为在函数组件中,每次点击add count按钮都会使得count自增一次=>导致Demo1函数重新执行一遍,形成一个新的闭包。每个闭包中的count值是相互独立的。点击alert按钮的这一瞬间形成的闭包中count的值为5,3s之后弹出来的这个值自然也为5。后面再次自增,形成的是新的闭包。互不干扰。

而在Class组件中由于都是在this的上下文中改变count的值,而不会形成隔离的闭包,因此,虽然点击alert按钮那一时刻count的值为5,但是随着后面点击add count按钮,count值不断自增。修改的都是this上面的值,3s后弹出的也就是count的最新值了。

在业务开发中,可能常常需要的是count的最新值,但其实在编程中,函数组件的行为才是正常的,类组件的行为反而有些’迷惑‘。正常情况下,本应该alert出我点击弹出按钮那一刻count的值,而不是之后的值。

额外说一下,我们在使用Function组件开发的时候,使用useState中的 setXXX如果期望使用当前XXX最新的值使用的是回调函数,会将当前XXX的值传入,否则返回的也是之前时刻闭包中的值而非最新的值,也是相同的道理。

为了加深对函数组件的闭包的理解,这里辅助再配置一个例子,如果不使用useState来改变count的值,而只是在函数中正常设置一个count的值得话,那又该如何呢? 代码如下

import React from "react";

const Demo4 = () => {
  let count = 0;
  const addCount = () => {
    count = count + 1;
    console.log(count);
  };

  const alertCount = () => {
    setTimeout(() => {
      alert(count);
    }, 3000);
  };

  return (
    <div>
      <p>函数组件中的普通变量</p>
      <button onClick={addCount}>add Count</button>
      <button onClick={alertCount}>alertCount</button>
      <div>count: 打开console查看count的值</div>
    </div>
  );
};

export default Demo4;

答案是3s之后alert出的是count的最新值,因为不使用usestate,该函数组件始终不会去更新,始终都是一个闭包。因此返回的值是最新的count的值

5、补充

有的时候在不同的业务场景下,需要即获得点击alert按钮那一时刻下的count值也需要获取count的最新值,该怎么办呢?

Fcuntion组件中可以使用useRef这个hook实现。代码如下:

import React, { useState, useRef } from "react";

const Demo2 = () => {
  let [count, setCount] = useState(0);
  // useRef 返回的是同一个对象,类似 {current:''}
  const countRef = useRef();
  countRef.current = count;

  const addCount = () => {
    setCount(count + 1);
  };

  const alertCount = () => {
    setTimeout(() => {
      alert(countRef.current);
    }, 3000);
  };

  return (
    <div>
      <button onClick={addCount}>add Count</button>
      <button onClick={alertCount}>alertCount</button>
      <div>count: {count}</div>
    </div>
  );
};

export default Demo2;

效果如下: React函数组件中的闭包陷阱 原因是使用了useRef这个hook每次返回的都是类似{current:""}这么一个相同的对象。在3s之后自然就会获取最新count的值了 在Class组件中只需要在alertCount函数中稍作修改,用一个变量保存点击alert按钮时刻 count的值即可了

 alertCount = () => {
    // 记录点击alert按钮那一时刻count的值
    const recordCount = this.state.count;
    setTimeout(() => {
      // 返回的始终是 this对象上的状态
      alert(
        `点击alert按钮那一时刻的count: ${recordCount}, 最新count: ${this.state.count}`
      );
    }, 3000);
  };

效果如下: React函数组件中的闭包陷阱

也可以直接去codesandbox中查看代码,调试一下:

Demo Online

转载自:https://juejin.cn/post/7255592202241196087
评论
请登录