4
头图
In front-end development, polling (setInterval) is often used, such as back-end asynchronous data processing, which needs to query the latest status regularly. But when polling with React Hook , you may find that setInterval is not so easy to control. Today, I will talk about how to solve the problem of setInterval invocation in project development, and how to use setInterval more elegantly.

introduction of problems

Let's start with a simple example. For ease of description, the case in this article is demonstrated with a counting timer.

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

export default function IntervalExp() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(timer);
  }, []);
  return (
    <div>
      <p>当前计数:{count}</p>
    </div>
  );
}

First, a count variable is defined using useState, and then in useEffect, a timer named timer is defined, and the count+1 operation is performed in the timer, and the timer is cleared when the component is unloaded.

Ideally, count will perform a +1 operation and continue to increment. But this is not the case. After count becomes 1, there will be no more changes. The reason is very simple. Since the dependent count object is not added to the dependent object array in useEffect, it gets the old count object every time, which is 0.

Method 1: Add a dependency array

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

export default function IntervalExp() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    console.log("更新了", timer);
    return () => clearInterval(timer);
  }, [count]);
  return (
    <div>
      <p>当前计数{count}</p>
    </div>
  );
}

When the count object is added to the dependency array, it can be found that the timer can now work properly. But note that there is a pit here. When returning, that is, when the component is unloaded, you must do a cleanup operation, otherwise your timer will execute faster and faster, because new timers will continue to be generated, but the old timer The device was not cleaned .

But is this way perfect? Otherwise, if the data operated by the timer contains props passed by the parent component, or other state, it needs to be added to the dependency array, which is not only unsightly, but also prone to errors. At the same time, there is a problem with this method, that is, the timer needs to be regenerated every time it changes, which will inevitably have a high performance loss .

Method 2: The way without adding a dependent array (useRef)

useRef is the official hook. The object defined by useRef has a current object, which can store data, and the stored data can be modified, and in each rendering of the component, the latest data can be obtained from the current. Based on this feature of ref, implement a custom hook named useInterval.

 import { useEffect, useRef } from "react";

export const useInterval = (cb: Function, time = 1000) => {
  const cbRef = useRef<Function>();
  useEffect(() => {
    cbRef.current = cb;
  });
  useEffect(() => {
    const callback = () => {
      cbRef.current?.();
    };
    const timer = setInterval(() => {
      callback();
    }, time);
    return () => clearInterval(timer);
  }, []);
};

In this custom hook, there are two parameters: callback function and polling time. Use useEffect to assign the latest callback function to ref.current, so that the second useEffect can get the latest callback from ref.current and execute it in the timer.
How to use it in the project?

 useInterval(() => {
    setCount(count + 1);
  }, 1000);

Just introduce a custom hook and call it in the format above.

Method 3: More advanced method (useReducer)☆☆☆

Looking back at the first example, why can't you achieve the desired timer effect without adding count in useEffect? To put it bluntly, it is because the state data is read, and the latest count data cannot be obtained because of the closure. So the interval operation fails. In fact, with the help of useReducer, the count data can be updated without reading the count.

 import React, { useEffect, useReducer } from "react";

function reducer(state: { count: number }) {
  return { count: state.count + 1 };
}
export default function IntervalExp() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  useEffect(() => {
    setInterval(() => {
      dispatch();
    }, 1000);
  }, []);

  return (
    <div>
      <p>当前计数{state.count}</p>
    </div>
  );
}

In this case, a simple count operation method is defined using useReducer, and the count data is successfully updated by calling the dispatch method in the interval. useReducer can be used in complex business logic scenarios that need to operate multiple states. Although it is cumbersome to define, it can extract the business logic in components and write code that is easier to maintain. In the current scenario, useReducer It is more elegant than the above two methods, and it is also the recommended method in this article.

Summarize

Hook is a very attractive invention in React. Using hook flexibly can write more quality code. The author's purpose in writing this article is also because he encountered this problem in actual development, so I hope this article can help other developers.

Reference article: Callback function in usestate _ Several methods of using setInterval in React Hooks


不羁的风
35 声望4 粉丝

天下事有难易乎? 为者,则难者亦易已;不为,则易者亦难矣!