第31节——react hooks 中的过期闭包问题

一、闭包

1、概念

闭包是一种特殊的 JavaScript 函数,它可以访问外部作用域内的变量。一个函数就是一个闭包,当它被定义在另一个函数内部,并且该内部函数可以访问到它的外部函数的变量。闭包的一个典型应用场景是实现 JavaScript 中的面向对象编程,在这种情况下,一个对象可以通过闭包来访问它的私有成员。闭包还可以用来实现模块化编程,把相关的代码封装在一起,避免变量命名冲突。

2、特点

1、闭包有自己的作用域,不受外部作用域的影响;

2、闭包可以访问外部作用域内的变量,并且可以保存这些变量的值;

3、闭包可以独立地存在,并且可以在以后被调用。

3、例子

  function add(num) {
    let value = 0
    return () => {
      value += num
      console.log(value)
    }
  }
  const fun = add(10)
  
  // 后续调用不传
  fun() // 10
  fun() // 20

二、过期闭包

1、概念

过期闭包(stale closure)是指一个闭包在创建之后,所引用的外部作用域内的变量已经被修改,但闭包内仍然保存了旧值。这就导致闭包中的代码与外部作用域内的实际状态不一致,从而造成错误的结果。

这种情况通常发生在动态生成闭包时,例如在循环中使用闭包。因为在每次循环中,闭包引用的外部变量都会发生变化,如果没有特别处理,闭包内的代码就会捕获到过期的外部变量。

2、过期闭包的例子

/**
运行下面的代码,每一秒会输出5个5,而不是0、1、2、3、4。
这是因为所有的闭包都引用了同一个外部变量i,并在setTimeout回调函数执行时访问了这个变量,
而这个变量已经在循环结束后被赋值为5了
*/
for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}


/**
这次,每一秒会输出0、1、2、3、4,而不是5个5。
因为闭包中引用了不同的变量,所以每个闭包都独立于外部作用域,
并且不会受到外部变量的影响

*/
for (var i = 0; i < 5; i++) {
  (function (index) {
    setTimeout(function () {
      console.log(index);
    }, 1000);
  })(i);
}

/**
let声明的变量是块级作用域的,在for循环中声明的let变量每次循环都会创建一个新的作用域。
因此,与var声明的变量不同,let声明的变量不会对整个循环造成影响
*/
for (let i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

四、react hook中的过时的闭包

1、概念

在React中,过期闭包问题是指因为闭包的生命周期长于其引用的变量的生命周期而导致的问题。

在React组件的render函数中,如果使用了闭包引用组件的state或props,当state或props发生变化时,闭包将不会自动更新引用的变量。这就可能导致闭包引用了错误的值,从而导致组件的不正确行为。

2、useEffect中过期闭包的表现

import React, { useState, useEffect, useContext } from 'react';

export default function hook() {

  const [count, setCount] = useState(0)
  /**
   * 每次点击都会调用,没切都是原来的值
   */
  useEffect(() => {
    // 是一个过时的闭包
    setInterval(() => {
      console.log(count)
    }, 2000)
  }, [])

  return (
    
{count}
) }

3、useEffect解决方案

让useEffect()知道定时器的方法里面中的闭包依赖于count

import React, { useState, useEffect, useContext } from 'react';

export default function hook() {

  const [count, setCount] = useState(0)
  /**
   * 每次点击都会调用,没切都是原来的值
   */
  useEffect(() => {
    // 是一个过时的闭包
    const ter = setInterval(() => {
      console.log(count)
    }, 2000)
    
    // 每次调用前先清空定时器,或者说重新创建
    return () => {
      clearInterval(ter)
    }
    
    // 这行是重点,count变化后重新渲染useEffect
  }, [count])

  return (
    
{count}
) }

4、useState过期闭包的表现

点击 +1 然后立即点击 +2,count 只更新到 1。这是因为 delay() 是一个过时的闭包

import React, { useState, useEffect, useContext } from 'react';

export default function hook() {

  const [count, setCount] = useState(0);

  /**
   *
   * delay() 是一个过时的闭包,它使用在初始渲染期间捕获的过时的 count 变量
   */
  function add() {
    setTimeout(function delay() {
      setCount(count + 1);
    }, 1000);
  }

  const add2 = () => {
    setCount(count + 2)
  }

  return (
    
{count}
) }

5、useState解决方案

import React, { useState, useEffect, useContext } from 'react';

export default function hook() {

  const [count, setCount] = useState(0);

  /**
   *
   * delay() 是一个过时的闭包,它使用在初始渲染期间捕获的过时的 count 变量
   */
  function add() {
    setTimeout(function delay() {
      setCount((a) => a + 1);
    }, 1000);
  }

  const add2 = () => {
    setCount(count + 2)
  }

  return (
    
{count}
) }

你可能感兴趣的:(react,react.js,前端,前端框架)