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