如何在不使用实际的useEffect钩子的情况下创建像React的useEffect钩子中那样的陈旧闭包?

2022-02-22 00:00:00 reactjs closures javascript react-hooks

我知道闭包是如何工作的,但是我不能理解在没有详尽的依赖数组的情况下,陈旧的闭包useEffect是如何在Reaction的useEffect中创建的。为此,我试图复制一个陈旧的闭包,就像在Reaction的useEffect中一样,不使用useEffect,但是我无法创建它。我的代码不会创建陈旧的闭包,而是在每个时间间隔记录一个正确值。请您看一下下面的片段,然后告诉我:

  1. 我做错了什么?当我们没有提供完整的依赖数组时,我应该怎么做才能创建像我们在Reaction的useEffect中得到的那样的陈旧闭包?(参考代码在文章的末尾)

  2. 当我们没有在useEffect中给出详尽的依赖关系时,为什么会创建陈旧的闭包?为什么useEffect钩子回调中的代码不像普通函数那样使用词法作用域,并打印实际值?

function createIncrement(incBy) {
  let value = 0;

  function increment() {
    value += incBy;
    console.log(value);
  }
  
  function useEffect(fun) {
    fun()
  }
  
  useEffect(function() {
    setInterval(function log() {
          // what should I do to create a stale closure here?
          // So that if I change the value it should show me the old value
          // as it does when using React's useEffect without exhaustive dependencies array

          console.log(`Count is: ${value}`); // prints correct value each time
        }, 2000);
  });
  
  setTimeout(() => {
    increment(); // increments to 5
    increment(); // increments to 6
  }, 5000);
  
  return [increment];
}

const [increment] = createIncrement(1);
increment(); // increments to 1
increment(); // increments to 2
increment(); // increments to 3
increment(); // increments to 4

为完整起见,下面是使用React的useEffect的代码片段,其中我们没有为Reaction的useEffect提供详尽的依赖项数组,因此创建了一个陈旧的闭包:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

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

  useEffect(function () {
    setInterval(function log() {
      // No matter how many times you increase the counter 
      // by pressing the button below,
      // this will always log count as 0
      console.log(`Count is: ${count}`);
    }, 2000);
  }, []);

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<WatchCount />, rootElement);

解决方案

我做错了什么?我应该怎么做才能创建一个陈旧的闭包,比如 我们在Reaction的Use Effect中得到的那个,当我们没有提供完整的 依赖项数组?

您不会得到陈旧的闭包,因为您只调用了createIncrement函数一次。

为了重新呈现功能性Reaction组件,Reaction再次调用组件函数。这将创建一个新作用域,该作用域与上次调用函数组件时创建的作用域没有链接。

若要获取过时的关闭,您需要多次调用createIncrement

function createIncrement(incBy, functionCallIdentifier, intervalDelay) {
  let value = 0;

  function increment() {
    value += incBy;
    console.log("inside call: " + functionCallIdentifier + " - value: " + value);
  }

  increment();
  
  function useEffect(fun) {
    fun();
  }

  useEffect(function () {
    setInterval(function log() {
      console.log("inside call: " + functionCallIdentifier + " - count: " + value);
    }, intervalDelay);
  });
}

createIncrement(1, "first call", 2000);

// calling again but 'setInterval' set in the first call will continue
// to log the latest value of variable "value" that it closed over, i.e. 1 
createIncrement(2, "second call", 4000);

为什么要创建过时的闭包,而我们没有给出详尽的 使用中的依赖项是否有效?为什么useEffect中的代码不影响 钩子的回调只需使用词法作用域,就像普通函数一样 是否打印实际值?

上面的代码示例应该会让您了解创建陈旧闭包的原因。useEffect的回调函数确实使用了词法作用域,但不同之处在于再次调用函数组件会创建一个新作用域,但是组件的上一次呈现中设置的useEffect钩子的旧回调函数将继续看到创建它的作用域中的值。

此行为不特定于反应-这是javascript中每个函数的行为方式:每个函数在其创建范围内关闭。

useEffect挂接中设置新回调之前,在清除前一个回调之前,旧回调将继续记录它关闭的作用域中的值。

下面的代码示例使用清理函数清除第一次调用createIncrement函数时设置的间隔。

function createIncrement(incBy, functionCallIdentifier, useEffectCleanupFn) {
  if (useEffectCleanupFn) {
    useEffectCleanupFn();
  }

  let value = 0;

  function increment() {
    value += incBy;
    console.log("inside call: " + functionCallIdentifier + " - value: " + value);
  }

  increment();
  
  let cleanupFn;

  function useEffect(fun) {
    cleanupFn = fun();
  }

  useEffect(function () {
    let id = setInterval(function log() {
      console.log("inside call: " + functionCallIdentifier + " - count: " + value);
    }, 2000);

    return () => clearInterval(id);
  });

  return cleanupFn;
}

let cleanupFn1 = createIncrement(1, "first call");

setTimeout(() => {
  createIncrement(2, "second call", cleanupFn1);
}, 4000);

相关文章