关于前端:理解为什么要给useEffect声明依赖

11次阅读

共计 2855 个字符,预计需要花费 8 分钟才能阅读完成。

在应用 useEffect、useCallback 这些 hooks,为什么还要写依赖数组呢,不写这些依赖,React 还给你正告。
上面我就来分享一下本人的了解

申明依赖的目标:避免函数的闭包个性产生意外的谬误

前言

在 b 站看到一个 up 用 JS 实现红绿灯,我就试着本人也写一个。
目标是想要它每个 1 秒跳转到下一个灯。
红 => 绿 => 黄 => 红 => 绿 => 黄
这样的程序

代码

然而我发现,上面这串代码只有绿灯和黄灯亮。

import React, {useState, useEffect} from "react";
import classes from "./TrafficLight.module.css";
import Light from "./Light";

let curIdex = 0;

const TrafficLight = () => {const [lights, setLights] = useState([{ on: true, color: "red", id: 0},
    {on: false, color: "green", id: 1},
    {on: false, color: "yellow", id: 2},
  ]);
  console.log('current lights:', lights);
  useEffect(() => {
    let curIdex = 0;

    const timer = setInterval(() => {
      curIdex += 1;
    if (curIdex >= lights.length) curIdex = 0;
    console.log('received lights', lights)
    toggleLight(lights[curIdex]);
    }, 1000 * 1);

  }, []);

    return () => clearTimeout(timer);
  }, [lights]);

  const toggleLight = (curLight) => {
    setLights(lights.map((light) =>
        light === curLight
          ? {...light, on: !light.on}
          : {...light, on: false}
      )
    );
  };

  const lightList = lights.map((light) => {return <Light key={light.id} light={light} onToggle={toggleLight} />;
  });

  return <div className={classes.container}>{lightList}</div>;
};

export default TrafficLight;

//--------------- 分割线 -----------------
import React from "react";
import classes from "./Light.module.css";

const Light = ({light, onToggle}) => {
  return (
    <div
      key={light.id}
      className={light.on ? `${classes.light} ${classes[light.color]}` : classes.light
      }
      onClick={() => onToggle(light)}
    />
  );
};

export default Light;

关上控制面板看一下,发现每次调用 toggleLight 时,获取的 lights 还是停留在一开始的状态
,尽管 TrafficLight 组件的 lights 曾经更新了,然而 toggleLight 函数获取到的 lights 并没有更新,所以才会导致每次红灯都不亮。

toggleLight 产生闭包的起因:

useEffect 依赖数组为空,只会在首次渲染时调用它。
TrafficLight 组件第一次渲染:
调用 useEffect 并申明了它的回调函数:

useEffect(() => {
    let curIdex = 0;

    const timer = setInterval(() => {
      curIdex += 1;
      if (curIdex >= lights.length) curIdex = 0;

      toggleLight(lights[curIdex]);
    }, 1000 * 1);
  }, []);

此时这个回调函数获取到的 lights 是咱们预设的 lights。
TrafficLight 这个组件函数执行结束后,函数产生的执行上下文也就从 stack 里移除了。
然而,setInterval 的回调函数又会在 1 秒之后执行,它更新了组件的 state,组件开始第二次渲染。

TrafficLight 组件二次渲染:
lights 此时曾经更新了,红灯的 on 变成了 false。然而 useEffect 不会被再次调用,因为它的依赖是空数组。这就导致 useEffect 的回调函数也不会被从新定义,setInteval 还是会调用第一次渲染时定义的回调函数。因为函数的词法作用域,导致这个函数始终获取不到更新后的数据。
为了防止这种状况,咱们就须要增加依赖数组。

总结

dependency,依赖,正如它名字所形容的一样,useEffect 的回调函数的失常运行,有时会依赖于某些内部变量。如果这些变量产生了扭转,回调函数获取到的变量却并不会同步更新。只从新定义这个回调函数,它能力获取到更新后的变量。从新定义它的回调函数,也就等于要从新调用 useEffect。useCallback 也是同样的情理

css 代码

.container {
  height: 20rem;
  width: 7rem;
  background-color: #2c3e50;
  border-radius: 3.5rem;
  padding: 0.7rem 0;

  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;

  position: fixed;
  top: 1rem;
  right: 2rem;
}
.light {
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
  background-color: rgba(0, 0, 0, 0.35);

  display: flex;
  justify-content: flex-start;
  align-items: center;
}

.light::after {
  content: "";
  display: block;
  width: 80%;
  height: 80%;
  border-radius: 50%;
  border-right: 4px solid rgba(255, 255, 255, 0.6);
}

.light.red {
  background-color: #c0392b;
  box-shadow: 0 0 20px 5px #c0392b;
}

.light.yellow {
  background-color: #f1c40f;
  box-shadow: 0 0 20px 5px #f1c40f;
}

.light.green {
  background-color: #2ecc71;
  box-shadow: 0 0 20px 5px #2ecc71;
}
正文完
 0