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