日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

React?Hooks與setInterval的踩坑問題小結_React

作者:田先森 ? 更新時間: 2022-06-25 編程語言

一、需求

我們希望有一個每一秒自動+1的定時器

function Counter() {
  let [count, setCount] = useState(0);
  useEffect(() => {
    let id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [count]);
  return <h1>{count}</h1>;
}

這種寫法你會發現頁面效果確實能出來,但是性能很差。每當 count 更改了, useEffect 就會渲染一次,定時器也會不停的被新增與移除。過程如下:

//第一次
function Counter() {
//... 
useEffect(() => {
    let id = setInterval(() => {
      setCount(0 + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [0]);
//...
}
//第二次
function Counter() {
//... 
useEffect(() => {
    let id = setInterval(() => {
      setCount(1 + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [1]);
//...
//第N次
}

現在我們的需求是在實現功能的基礎上,還要使得定時器只監聽一次,保障它的性能很高。

二、解決方案

1、函數式更新

useState 中的set方法可接收函數,該函數將接收先前的 state ,并返回一個更新后的值。這樣定時器每次拿到的是最新的值。

function Counter() {
let [count, setCount] = useState(0);
useEffect(() => {
    let id = setInterval(() => {
      setCount(v => {
        return v + 1;
      });
    }, 1000);
    return () => clearInterval(id);
  }, []);
return <h1>{count}</h1>;
}

2、使用useRef

useRef 返回一個可變的 ref 對象,返回的 ref 對象在組件的整個生命周期內保持不變。

將定時器函數提取出來,每次定時器觸發時,都能取到最新到 count .

function Counter() {
  let [count, setCount] = useState(0);
  const myRef = useRef(null);
  myRef.current = () => {
    setCount(count + 1);
  };
  useEffect(() => {
    let id = setInterval(() => {
      myRef.current();
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return <h1>{count}</h1>;
}

思考:為什么不直接像下面這個例子,將setInterval寫成 setInterval(myRef.current, 1000)這樣呢?為什么要通過一個函數返回?

//這個例子是錯誤的
function Counter() {
  let [count, setCount] = useState(0);
  const myRef = useRef(null);
  myRef.current = () => {
    setCount(count + 1);
  };
  useEffect(() => {
    let id = setInterval(myRef.current, 1000);
    return () => clearInterval(id);
  }, []);
 return <h1>{count}</h1>;
}

定時器的第一個參數為 interval 變量,如果直接將myRef.current直接賦值給 interval 變量,那么之后的myRef.current的值改變之后,在這里依舊取到的是改變之前的值,因為ref的改變不會引起組件的重新渲染。

3、用useReducer

將 count 變量存入 reducer 中,使用 useReducer 更新 count

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state + 1;
    default:
      throw new Error();
  }
}
?
function Counter() {
  const [state, dispatch] = useReducer(reducer, 0);
  useEffect(() => {
    setInterval(() => {
      dispatch({ type: "increment" });
    }, 1000);
  }, []);
  return <h1>{state}</h1>;
}

4、自定義的hooks

自定義hook:useInterval

import React, { useState, useEffect, useRef } from 'react';
 
function useInterval(callback, delay) {
  const savedCallback = useRef();
 
  // 保存新回調
  useEffect(() => {
    savedCallback.current = callback;
  });
 
  // 建立 interval
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

使用useInterval

function Counter() {
  let [count, setCount] = useState(0);
 
  useInterval(() => {
    // 你自己的代碼
    setCount(count + 1);
  }, 1000);
 
  return <h1>{count}</h1>;
}

useInterval這個api的設計是非常巧妙的。

  • 首先useIntervalsetInterval接收的參數是一樣的,這就降低了我們的學習成本

  • 其次,useInterval的delay參數是可以動態調整的,而setInterval的delay參數是沒有辦法動態調整的

    • useInterval Hook 接收到不同 delay,它會重設 interval。
    • 聲明一個帶有動態調整 delay 的 interval,來替代寫 添加和清除* interval 的代碼 —— useInterval Hook 幫我們做到了**。
    • 如果想要暫時暫停 interval ,那么可以像下面這個例子一樣
  const [delay, setDelay] = useState(1000);
  const [isRunning, setIsRunning] = useState(true);
 
  useInterval(() => {
    setCount(count + 1);
  }, isRunning ? delay : null);
  • useInterval的delay也可以受控于另外一個useInterval
function Counter() {
  const [delay, setDelay] = useState(1000);
  const [count, setCount] = useState(0);
 
  // 增加計數器
  useInterval(() => {
    setCount(count + 1);
  }, delay);
 
  // 每秒加速
  useInterval(() => {
    if (delay > 10) {
      setDelay(delay / 2);
    }
  }, 1000);
 
  function handleReset() {
    setDelay(1000);
  }
 
  return (
    <>
      <h1>Counter: {count}</h1>
      <h4>Delay: {delay}</h4>
      <button onClick={handleReset}>
        Reset delay
      </button>
    </>
  );
}

原文鏈接:https://juejin.cn/post/7090822385430953992

欄目分類
最近更新