了解React exhaustive-deps linting警告

1,427 阅读5分钟

目录

当你在React中从使用类组件转移到功能组件时,可能会很颠簸。你必须学习如何使用React Hooks。最有可能的是,你学到的第一个是useState 。那里没有什么大问题,因为它的工作原理与setState

但后来你必须根据道具来更新状态,这意味着是时候尝试一下useEffect 。这看起来也很简单,一开始:你读了关于依赖数组的内容,这很有意义。但是在使用useEffect ,也很容易走错路。让我们看一个例子。

const App = () => {
   const [randomNumber, setRandomNumber] = useState();
   React.useEffect(() => {
     setRandomNumber(Math.random());
   }, [randomNumber]);
 }

上面的代码中的错误是非常明显的,你可以在这里看到结果。上面的代码创造了一个无尽的循环。但是,你使用useEffect 的另一种错误方式是创建一个陈旧的闭包,这可能更难发现,并导致可能难以追踪的问题。

useEffect 中的一个变量从未更新时就会发生这种情况。有时这是你想要的,但大多数情况下,你并不想要。下面是一个陈旧闭合的例子。

const App() {
  const [count, setCount] = useState(0);
  useEffect(function () {
    setInterval(function log() {
      console.log(`Count is: ${count}`);
    }, 2000);
  }, []);

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

你可以在这里看到这段代码的运行。当你点击按钮时,显示在组件中的count 变量会更新,但每两秒钟被记录在useEffect 的值一直保持在0 。我们正期待着这一点,但随着你的代码变得越来越复杂,可能会更难找到问题所在。幸运的是,你可以使用ESlint插件来为你找到它,在它成为一个bug之前。

什么是详尽的deps lint规则?

如果你把鼠标悬停在代码例子中的依赖性数组下的斜线上,你会看到lint为什么会生气。

Missing Dependency

你会看到,lint给了你几个选择:要么把count ,要么把依赖性数组完全删除。如果你删除了依赖性数组,里面的函数将在每次渲染时运行。我想这很好,但这违背了最初设置useEffect 的目的。

明显的答案是将count 变量添加到依赖阵列中。在VS Code通过ESlint扩展,以及在其他具有类似功能的IDE中,你可以点击快速修复链接,count 将为你添加到依赖数组中。

提示是非常有争议性的。大多数开发者认为应该这样做,但他们对如何做有很大分歧。许多规则,如关于缩进、大括号的间距和其他规则,更多的是关于可读性,而不是关于确保良好的代码。

[eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)可以让你不至于在React Hooks上犯错,而这些错误是很难追踪和调试的。这个ESlint插件将确保你在React代码中遵循Hooks的规则,这就是。

  • 只在顶层调用Hooks
  • 只从React函数中调用Hooks

它还会检查你的Hooks中的依赖数组,以确保你从它们那里得到你期望的功能。

如何在React项目中添加这一规则

如果你使用Create React App,React Hooks的ESlint插件已经默认包含。要把它添加到现有的项目中,只需用npm或yarn安装它。

npm install eslint-plugin-react-hooks --save-dev
yarn add eslint-plugin-react-hooks --dev

接下来,在你的ESlint配置中添加以下内容。

// ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // For checking rules of hooks
    "react-hooks/exhaustive-deps": "warn" // For checking hook dependencies 
  }
}

你还需要在你的IDE中添加ESlint扩展,以便更容易纠正警告。对于VS Code,你可以使用这个扩展,对于Atom,你可以使用这个

如何修复详尽的部署警告

一旦你在ESlint配置中加入了详尽的deps规则,你可能会遇到一些警告,这些警告可能需要比我们第一个例子更多的思考。你可以在GitHub上找到一长串关于这个规则的评论,在这些评论中,使用这个规则的开发者并不清楚为什么他们会得到一个警告,或者如何解决这个问题。

我们得到的第一个详尽的deps警告是因为依赖关系数组中缺少一个原始变量。

const App() {
  const [count, setCount] = useState(0);
  useEffect(function () {
    console.log(`Count is: ${count}`);
  }, []);

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

要知道为什么会有警告,以及为什么你在IDE中点击快速修复得到的修复会起作用,这很简单。添加计数变量会解决这个问题,不会引起任何奇怪的问题。但有时,在使用建议的解决方案之前,必须对其进行检查。

使用对象和数组

export default function App() {
  const [address, setAddress] = useState({ country: "", city: "" });

  const obj = { country: "US", city: "New York" };

  useEffect(() => {
    setAddress(obj);
  }, []);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

你可以在这里看到这段代码的实况。注意有一个关于依赖数组需要obj 变量的警告。很奇怪!它总是相同的值,那么为什么会发生这种情况呢?

JavaScript中的对象和数组是通过引用来比较的,而不是通过值。每次组件渲染时,这个对象有相同的值,但有不同的引用。如果这个变量是一个数组,同样的事情也会发生。

为了消除警告,你可以把obj 变量添加到数组中,但这意味着useEffect 中的函数将在每次渲染时运行。这也是点击 "快速修复"所要做的,而不是你真正想要的应用程序的运作方式。一个解决方案是在依赖性数组中使用对象的一个属性。

  useEffect(() => {
    setAddress(obj);
  }, [obj.city]);

另一个选择是使用useMemo 钩子来获得该对象的一个记忆值。

const obj = useMemo(() => {
    return { country: 'US', city: 'New York' };
  }, []);

你也可以把obj 变量移到useEffect ,或者移到组件外,以消除警告,因为在这些位置,它不会在每次渲染时被重新创建。

// Move it here
// const obj = { country: "US", city: "New York" };
export default function App() {
  const [address, setAddress] = useState({ country: "", city: "" });

  const obj = { country: "US", city: "New York" };

  useEffect(() => {
    // Or here
    // const obj = { country: "US", city: "New York" };

    setAddress(obj);
  }, []);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

上面的例子是为了说明在依赖阵列警告似乎没有意义的情况下,修复它的可能方法。下面是一个你可能想在你的代码中注意的例子。

你确实想在不禁用lint规则的情况下解决这个警告,但是,就像上面的例子一样,使用lint建议会导致useEffect 中的函数不必要地运行。在这个例子中,我们有道具被传入一个组件。

import { getMembers } from '../api';
import Members from '../components/Members';

const Group = ({ group }) => {
  const [members, setMembers] = useState(null);

  useEffect(() => {
    getMembers(group.id).then(setMembers);
  }, [group]);

  return <Members group={group} members={members} />
}

如果group 道具是一个对象,React会检查当前的渲染是否指向先前渲染中的同一个对象。因此,即使对象是相同的,如果为后续渲染创建了一个新的对象,useEffect

我们可以通过检查group 对象中的特定属性来解决这个问题,因为我们知道这个属性会发生变化。

useEffect(() => {
    getMembers(group.id).then(setMembers);
  }, [group.id]);

如果任何一个值可能发生变化,你也可以使用useMemo Hook。

import { getMembers, getRelatedNames } from '../api';
import Members from '../components/Members';

const Group = ({ id, name }) => {
  const group = useMemo(() => () => return { id, name }, [
    id,
    name,
  ]);
  const [members, setMembers] = useState(null);
  const [relatedNames, setRelatedNames] = useState(null);

  useEffect(() => {
    getMembers(id).then(setMembers);
    getRelatedNames(names).then(setRelatedNames);
  }, []);

  return <Members group={group} members={members} />
}

在这里,当idname 的值发生变化时,group 变量将被更新,而useEffect 只有在group 常数发生变化时才会运行。我们也可以简单地将这些值添加到依赖关系数组中,而不是使用useMemo

处理缺失的函数

有的时候,lint警告会告诉你在一个数组中缺少一个函数。这将发生在任何有可能关闭超过状态的时候。下面是一个例子。

const App = ({ data }) => {
  const logData = () => {
    console.log(data);
  }

  useEffect(() => {
    logData();
  }, [])
}

这段代码会有一个lint警告,你可以在这里看到,建议你把logData 加入到依赖数组中。这是因为它使用了data 这个道具,这个道具可能会改变。为了解决这个问题,你可以按照建议把它添加到依赖关系数组中,或者,这样做。

const App = ({ data }) => {
  useEffect(() => {
    const logData = () => {
      console.log(data);
    }

    logData();
  }, [])
}

结论

由于React Hooks的复杂性和难以发现的问题,如果你在开发你的应用程序时走错了路,你可能会产生这样的问题,在Hooks规则中加入详尽的deps lint规则是必要的。这些规则将节省你的时间,并立即告诉你什么时候做错了。

有了正确的IDE扩展,甚至可以为你修复问题。虽然通常 "快速修复 "的解决方案是有效的,但你应该始终检查你的代码,了解为什么会有警告。这样,如果你想防止不必要的渲染,你会找到最好的修复方法,并可能找到一个更好的解决方案。