闭包概念是掌握React.JS的关键 - Nitsan


在本文中,展示理解闭包如何提高您的 React 技能。
闭包意味着,当 JavaScript 运行您的代码时,它会查找您函数中的所有变量,如果它在函数内部没有声明, 但在外部作用域(函数嵌套的地方)它“锁定”指定函数内该变量的值。

const myOuterFunction = () => { 
  let variableInOuterFunction = 'Hello World';
  const myNestedFunction = () => { 
    console.log(variableInOuterFunction); 
  };
  myNestedFunction(); 
};
myOuterFunction();

请注意我们如何在“myNestedFunction”中使用“variableInOuterFunction”的(在 myOuterFunction 中声明)。那是一个闭包。值“Hello World”现在在嵌套函数中“闭包”(锁定)。
听起来很简单,对吧?让我们看看理解它如何帮助我们在 React 中。
 
React 中的闭包
所以,让我们看看闭包在 React 中是如何工作的。

import React, { useEffect, useState } from 'react';
const ClosuresInReact = () => {
  const [count, setCount] = useState('1');
  const incrementCount = () => {
    setCount(prevCount => +prevCount + 1);
  };
  useEffect(() => {
    const timer = setTimeout(() => {
      incrementCount();
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [incrementCount]);
  return <div>{`Timer started: ${count}`}</div>;
};
export default ClosuresInReact;

这是一个简单的 React 组件,它在屏幕上打印一个计时器,该计时器每秒递增。
我们使用 useState 钩子来跟踪 count 变量的变化,然后我们有 incrementCount 函数,它使用 setCount 函数增加 count 变量,最后,我们有 useEffect 钩子,负责每秒触发 incrementCount 函数,使用 JavaScript 内置的函数 setTimeout。
有经验的 React 开发人员可能已经注意到,我们在 useEffect 的依赖数组中挂钩了函数 incrementCount。实际上,可能没有必要在此处包含它,但始终使用 useEffect 钩子中使用的所有变量(和函数)填充依赖项数组被认为是一种很好的做法。
现在我们有另一个问题。使用 incrementCount 函数作为 useEffect 钩子中的依赖项,我的 IDE 向对我大喊:Add alt text
我们现在不会深入探讨为什么会发生这种情况,我只想说这与函数是引用类型的事实有关,如果我们不将它移动到 useEffect 钩子内或用 useCallback 钩子包装它,我们可以导致无限循环。无论如何,对我们来说,现在重要的是来自 IDE 的建议——要么将 incrementCount 移动到 useEffect 中,要么用 useCallback 钩子包装它。让我们选择第二个选项。

import React, { useCallback, useEffect, useState } from 'react';
const ClosuresInReact = () => {
  const [count, setCount] = useState('1');
  const incrementCount = useCallback(() => {
    setCount(prevCount => +prevCount + 1);
  }, []);
  useEffect(() => {
    const timer = setTimeout(() => {
      incrementCount();
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [incrementCount]);
  return <div>{`Timer started: ${count}`}</div>;
};
export default ClosuresInReact;

现在一切都应该正常了,对吧?我的意思是即使我的 IDE 现在也平静了,让我们运行代码吧!
我们看到 Timer started:1, 2 和……发生了什么?计时器刚停在 2 点?现在,为什么会这样?
原因——你猜对了——闭包!如果我们仔细观察,我们会发现在 incrementCount 函数中我们实际上有一个闭包。
在外部函数 ClosuresInReact 中声明的变量 count 的值现在被“锁定”在函数 incrementCount 中。更准确地说,我们在 setCount 函数中所做的增量被锁定在 incrementCount 函数内,这就是我们在屏幕上看到 1、2 和...停止的原因。
 
如何修复 React 中的闭包问题
现在我有一个 useEffect 钩子,它有 incrementCount 作为依赖项。这意味着我应该只在 incrementCount 改变时重新渲染。我用 useCallback 钩子包裹了 incrementCount。这意味着我应该记住它的第一个闭包(当它第一次运行时),即使发生重新渲染,我也不应该使用刷新后的环境(计数现在为 2)运行该函数,而是使用我捕获的旧的第一个闭包(当计数为 1 时)。
useEffect 告诉我 incrementCount 没有改变,我不应该触发新的渲染。
解决方法很简单。我们需要将 count 变量作为依赖项传递给 useCallback 钩子。通过这个,我们告诉 React,即使我们想要保留 incrementCount 函数的第一个闭包,React 也应该使用 count 变量的最新值来跟踪和更新闭包。通过这样做,我们还能让 useEffect 钩子在屏幕上调用重新渲染,因为 incrementCount 每秒都在变化。

import React, { useCallback, useEffect, useState } from 'react';
const ClosuresInReact = () => {
  const [count, setCount] = useState('1');
  const incrementCount = useCallback(() => {
    setCount(prevCount => +prevCount + 1);
  }, [count]);
  useEffect(() => {
    console.log('useEffect');
    const timer = setTimeout(() => {
      incrementCount();
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [incrementCount]);
  return <div>{`Timer started: ${count}`}</div>;
};
export default ClosuresInReact;

现在一切都按预期工作。有趣的是,我的 IDE 现在仍然对我大喊大叫:Add alt text
它告诉我要从依赖数组中删除计数count,这当然是一个错误,它的发生是因为我们实际上并没有在 setCount 函数中使用 count 变量,但这只是再次向我们展示了理解闭包对于成为一名优秀的 React 开发人员的重要性,因为没有它我们就无法解决这个问题。