动态

详情 返回 返回

理解為什麼要給useEffect聲明依賴 - 动态 详情

在使用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;
}

Add a new 评论

Some HTML is okay.