React Hooks useEffect使用定時器,每5秒更新一次值


React useEffect中使用定時器所產生的閉包陷阱

其實關於這個問題在知乎和百度上都有提及,但是在掘金上卻沒有詳細的文章,因此准備出一篇文章來解決在useEffect中如何使用定時器。有一篇文章寫得特別好,如果大家想深入理解可以點擊此鏈接 使用 React Hooks 聲明 setInterval

眉頭一皺,發現問題並不簡單

首先讓我們做一個小Demo,設置一個名為value的state,並每隔5秒,產生一個隨機數,並讓value加上這個數

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;

這里看起來是實現了每隔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發生變化了,而且又多生成了一個定時器。如下圖所示。

 

 這對於瀏覽器性能來說是絕對不可以接受的,那這是為什么產生的呢,產生的原因是因為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!

原文鏈接:https://juejin.im/post/5e8540a1f265da47c8011d29


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM