React useEffect中使用定时器所产生的闭包陷阱

前言

其实关于这个问题在知乎和百度上都有说起,可是在掘金上却没有详细的文章,所以准备出一篇文章来解决在useEffect中如何使用定时器。有一篇文章写得特别好,若是你们想深刻理解能够点击此连接 使用 React Hooks 声明 setIntervalcss

眉头一皱,发现问题并不简单

首先让咱们作一个小Demo,设置一个名为value的state,并每隔5秒,产生一个随机数,并让value加上这个数react

import React, { useState, useEffect } from 'react';

import './App.css';

function App() {
  const [value, setValue] = useState<number>(0);
  useEffect(() => {
    const timer: NodeJS.Timeout = setInterval(() => {
      const random = (Math.random() * 10) | 0;

      setValue(value + random);
    }, 5000);

    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div>{value}</div>;
}

export default App;

复制代码

你自信满满的打开了浏览器,too simple! 忽然发现了value的值只发生了一次变化,你检查发现,原来useEffect的依赖性并无填入,因而你当心翼翼的将value填入依赖性数组

useEffect(() => {
    // .....
  }, [value]);
复制代码

perfect!,代码如期执行,每隔5秒都会增长一个值,内心想着‘不愧是我!’浏览器

你觉得的你觉得的不是你觉得的

若是咱们在代码中加入一个定时器数组,用来记录你添加了多少个定时器闭包

function App() {
  const [value, setValue] = useState<number>(0);
  const [timers, setTimers] = useState<Array<NodeJS.Timeout>>([]);
  useEffect(() => {
    const timer: NodeJS.Timeout = setInterval(() => {
      const random = (Math.random() * 10) | 0;
      setValue(value + random);
    }, 5000);
    timers.push(timer);
    setTimers(timers);
    console.log(timers);
    return () => {
      clearInterval(timer);
    };
  }, [value]);
  return <div>{value}</div>;
}

复制代码

你会惊讶的发现你不只value发生变化了,并且又多生成了一个定时器。以下图所示。 dom

这对于浏览器性能来讲是绝对不能够接受的,那这是为何产生的呢, 产生的缘由是由于useEffect 在第一次渲染时获取值为 0 的 value,将再也不次执行 effect,因此 setInterval 一直引用第一次渲染时的闭包 value,所以每次都是在0的基础上添加一个随机数,而不是依次累加,最关键的是若是你开启了Vscode的eslint插件,它还会给你自动补齐依赖项, 这里推荐一篇关于react-hooks闭包问题的文章 陈旧闭包问题,解释的比较清楚。

不要慌,问题不大

为了解决这个问题,咱们须要引入react中另一个hook ,useRef。useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变,利用这个特性,咱们把它用在咱们的demo中看看效果性能

function App() {
  const [value, setValue] = useState<number>(0);
  const [timers, setTimers] = useState<Array<NodeJS.Timeout>>([]);
  const saveCallBack: any = useRef();
  const callBack = () => {
    const random: number = (Math.random() * 10) | 0;
    setValue(value + random);
  };
  useEffect(() => {
    saveCallBack.current = callBack;
    return () => {};
  });
  useEffect(() => {
    const tick = () => {
      saveCallBack.current();
    };
    const timer: NodeJS.Timeout = setInterval(tick, 5000);
    timers.push(timer);
    setTimers(timers);
    console.log(timers);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div>{value}</div>;
}
复制代码

使用了useRef后,定时器不会被重复建立,可是value的值变成了依次累加,达到了预期的效果,真是让人神清气爽,so cool!spa

相关文章
相关标签/搜索