如何在不使用实际的useEffect钩子的情况下创建像React的useEffect钩子中那样的陈旧闭包?
我知道闭包是如何工作的,但是我不能理解在没有详尽的依赖数组的情况下,陈旧的闭包useEffect
是如何在Reaction的useEffect
中创建的。为此,我试图复制一个陈旧的闭包,就像在Reaction的useEffect
中一样,不使用useEffect
,但是我无法创建它。我的代码不会创建陈旧的闭包,而是在每个时间间隔记录一个正确值。请您看一下下面的片段,然后告诉我:
我做错了什么?当我们没有提供完整的依赖数组时,我应该怎么做才能创建像我们在Reaction的
useEffect
中得到的那样的陈旧闭包?(参考代码在文章的末尾)当我们没有在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);
相关文章