原文:https://medium.com/swlh/useeffect-4-tips-every-developer-should-know-54b188b14d9c

本文将谈论使用React hooks中的useEffect,并分享四个使用useEffect的小技巧。

1.每个useEffect只做一件事情

在React Hooks中,你可以同时使用多个useEffect。这是一个非常重要的特性——它可以帮助你编写更清晰的代码,你可以清楚的知道每个useEffect代码对应的单一职责所在(好比一句话只需要清楚表达出一个意思)。
把useEffect拆分成短小精悍的单一职责函数也会避免一些预期之外的bug发生(当使用依赖数组时—useEffect的第二个参数)。
比如,我们有两个变量AB,你想要编写一个基于useEffect和setTimeout的计数器,你可能会写出以下糟糕的代码:

  1. function App() {
  2. const [varA, setVarA] = useState(0);
  3. const [varB, setVarB] = useState(0);
  4. // Don't do this!
  5. useEffect(() => {
  6. const timeoutA = setTimeout(() => setVarA(varA + 1), 1000);
  7. const timeoutB = setTimeout(() => setVarB(varB + 2), 2000);
  8. return () => {
  9. clearTimeout(timeoutA);
  10. clearTimeout(timeoutB);
  11. };
  12. }, [varA, varB]);
  13. return (
  14. <span>
  15. Var A: {varA}, Var B: {varB}
  16. </span>
  17. );
  18. }

可以看到,一旦变量A(varA)和变量B(varB)发生改变,都会触发useEffect的重新执行,这会导致代码
逻辑不能正确的执行(varA和varB值不能正确的增加)。
虽然这只是一个简单的例子,但很明显一但函数中代码量不断增长,这些问题就很容易在编写过程中被忽视而埋藏下更多的bug,所以你应当将你的useEffect进行拆分。
上面例子的代码应该变成:

function App() {
  const [varA, setVarA] = useState(0);
  const [varB, setVarB] = useState(0);
  // Correct way
  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, [varA]);
  useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000);
    return () => clearTimeout(timeout);
  }, [varB]);
  return (
    <span>
      Var A: {varA}, Var B: {varB}
    </span>
  );
}

注:这里的代码只是一个例子,为了能让你更好的了解到useEffect可能遇到的问题,通常情况下当一个变量依赖于它的前一个状态的值,我们推荐使用setVarA(varA => varA + 1) 来代替文中的写法。

2.尽可能的使用自定义hooks

我们再来观察一下上面的例子,如果varA和varB完全不相关我们可以怎么处理?
在下面的代码中,我们简单的创建来一个自定义hook来隔离每个变量,这种方式可以让函数和变量的关联更加明确。

function App() {
  const [varA, setVarA] = useVarA();
  const [varB, setVarB] = useVarB();
  return (
    <span>
      Var A: {varA}, Var B: {varB}
    </span>
  );
}
function useVarA() {
  const [varA, setVarA] = useState(0);
  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, [varA]);
  return [varA, setVarA];
}
function useVarB() {
  const [varB, setVarB] = useState(0);
  useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000);
    return () => clearTimeout(timeout);
  }, [varB]);
  return [varB, setVarB];
}

现在每个变量都有自己唯一的hook,让代码更易维护更易读。

3.用正确的方式在不同条件下运行useEffect

关于setTimeout,我们看看下面的例子

function App() {
  const [varA, setVarA] = useState(0);
  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, [varA]);
  return <span>Var A: {varA}</span>;
}

假设需求要求我们限制计数器最大值不能超过5,我们来看看正确的写法和错误的写法。

// 错误的写法
function App() {
  const [varA, setVarA] = useState(0);
  // 不要这样做
  useEffect(() => {
    let timeout;
    if (varA < 5) {
      timeout = setTimeout(() => setVarA(varA + 1), 1000);
    }
    return () => clearTimeout(timeout);
  }, [varA]);

  return <span>Var A: {varA}</span>;
}

虽然这样也能让代码运行,但你需要牢记clearTimeout会在每次varA改变时执行,而setTimeout则不是每次都执行。
正确在useEffect中使用条件判断的方式,如下:

// 正确的写法 
function App() {
  const [varA, setVarA] = useState(0);
  useEffect(() => {
    if (varA >= 5) return;
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, [varA]);
  return <span>Var A: {varA}</span>;
}

你可以在 Material UI 中看到类似的使用,这能减少你在使用useEffect时犯错的可能。

4.将所有useEffect中使用的prop添加到依赖数组之中

如果你正在使用ESLint,你就会遇到类似的警告信息,参见:exhaustive-deps rule.
这是非常重要的,当你应用的代码量越来越庞大,更多的依赖,需要添加到useEffect中的依赖数组中。 (参见官方文档)
同样,我们再回到setTimeout的话题,我们的需求现在是需要只运行一次setTimeout,并且将varA加1。

错误的代码:

function App() {
  const [varA, setVarA] = useState(0);
  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, []); // 避免这样使用: varA 不在依赖数组之中!

  return <span>Var A: {varA}</span>;
}

虽然代码仍然可以正常运转,但我们想一想,如果以后代码变得越来约庞大,如果你需要把代码更改成其他的一些业务逻辑。在上面的例子中,你会更乐意看到所有依赖的变量都被声明出来,他会让你更容易的测试和检查出可能存在的bug(比如stale props和stale closures,陈旧属性和陈旧闭包,关于这个话题可以参考https://zhuanlan.zhihu.com/p/96372201
正确的代码:

function App() {
  const [varA, setVarA] = useState(0);
  useEffect(() => {
    if (varA > 0) return;
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, [varA]); // 使用这样的方式

  return <span>Var A: {varA}</span>;
}

Done!如果有任何问题,都欢迎留言相互讨论交流。
欢迎关注我的Javascript weekly,争取每周更新一篇对大家有用的文章。