美文网首页
React 自定义 Hooks 之 useSetInterval

React 自定义 Hooks 之 useSetInterval

作者: bowen_wu | 来源:发表于2020-04-26 19:59 被阅读0次

概述

在业务场景中,我们总会遇到倒计时。例如发送验证码之后的 60s 重新发送的倒计时。最近在使用 React Hook API 的时候,认为可以自定义一个 Hook,实现倒计时,于是就有了 useSetInterval 自定义 Hook

useSetInterval

这里是 demo。可以直接复制下面代码,之后在需要的页面进行引入

useSetInterval

import { useEffect, useRef } from "react";

function useSetInterval(callback, delay) {
  if (!(callback instanceof Function)) {
    throw new Error("callback 参数必须是函数!");
  }
  if (!(delay === null || typeof delay === "number")) {
    throw new Error("delay 必须是 null 或者数字!");
  }
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay === null) {
      return;
    }
    let id = null;
    const tick = () => {
      const returnValue = savedCallback.current();
      if (returnValue) {
        console.log("come in");
        if (returnValue instanceof Function) {
          returnValue();
        } else {
          throw new Error("返回值必须是函数!");
        }
        clearTimeout(id);
        return;
      }
      id = setTimeout(tick, delay);
    };
    id = setTimeout(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
}

export default useSetInterval;

使用如下

  useSetInterval(() => {
    if (count <= 0) {
      return () => {
        // 定时器消除后的代码逻辑
        setDelay(null);
        setCount(6);
      };
    }
    setCount(count - 1);
  }, delay);

React Hooks 实现倒计时

  useEffect(() => {
    let timerId = null;
    const run = () => {
      console.log("count -> ", count);
      if (count <= 0) {
        return () => {
          timerId && clearTimeout(timerId);
        };
      }
      setCount(count - 1);
      timerId = setTimeout(run, 1000);
    };
    timerId = setTimeout(run, 1000);
    return () => {
      timerId && clearTimeout(timerId);
    };
  }, [count]);

使用 React Hooks 实现倒计时是很容易的,这里是demo。这里我们需要注意一点,这里的 effect 是必须要清除的,这样才能保证我们一直只有一个定时器。另外请注意 React 何时清除 effect,React 会在组件卸载的时候执行清除操作,并且 effect 在每次渲染的时候都会执行,即 React 会在执行当前 effect 之前对上一个 effect 进行清除,当然可以传递第二个可选参数数组通知 React 跳过对 effect 的调用,从而进行性能优化

注:这里使用了 setTimeout 模拟 setInterval为啥使用 setTimeout 的原因

点击触发倒计时

  const sendCaptcha = () => {
    setSendingCaptcha(true);
    const run = () => {
      console.log("count -> ", count);
      if (count <= 0) {
        console.log("stop");
        setSendingCaptcha(false);
      }
      setCount(count - 1);
      setTimeout(run, 1000);
    };
    setTimeout(run, 1000);
  };

这里是demo,由于没有使用 useEffect,从而使得 sendCaptcha 方法拿不到最新的 count,所以 count 一直是5

解决上述问题

  useEffect(() => {
    let timerId = null;
    if (!sendingCaptcha) {
      return () => {
        timerId && clearTimeout(timerId);
      };
    }
    const run = () => {
      if (count <= 0) {
        setSendingCaptcha(false);
        return;
      }
      setCount(count - 1);
      timerId = setTimeout(run, 1000);
    };
    timerId = setTimeout(run, 1000);
    return () => {
      timerId && clearTimeout(timerId);
    };
  }, [sendingCaptcha, count]);

这里是demo,这里将 sendingCaptchacount 作为 useEffect 的依赖,从而实现了点击之后去触发倒计时

抽离 useSetInterval

设计 API

其中只需要开发者去关注自己的逻辑,而不用去关心副作用。
使用和 setInterval 相同,但是如果要停止必须要返回一个函数,函数内可以写开发者自己的逻辑,例如初始化 count,并且还要传递一个 interval 时间间隔

  useSetInterval(() => {
    if(count <= 0) {
      return () => {
        // 这个函数可以写一些定时器消除后的代码逻辑
        setCount(6);
      }
    }
    setCount(count - 1);
  }, delay);

1. 实现倒计时

function useSetInterval(callback, delay) {
  useEffect(() => {
    let timerId = null;
    const run = () => {
      const returnValue = callback();
      if (returnValue) {
        if (returnValue instanceof Function) {
          returnValue();
        } else {
          throw new Error("返回值必须是函数!");
        }
        timerId && clearTimeout(timerId);
        return;
      }
    };

    timerId = setTimeout(run, delay);
    return () => {
      timerId && clearTimeout(timerId);
    };
  }, [callback, delay]);
}

这里是demo。打开控制台可以发现 count 一直在循环,原因在于 使用 useSetInterval 时每次给到的 callback 都是新的。这时我们可以来个容器,这个容器指向 callback

2. 使用 useRef 保存 callback

  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  
  useEffect(() => {
    let timerId = null;
    const run = () => {
      const returnValue = savedCallback.current();
      if (returnValue) {
        if (returnValue instanceof Function) {
          returnValue();
        } else {
          throw new Error("返回值必须是函数!");
        }
        timerId && clearTimeout(timerId);
        return;
      }
      timerId = setTimeout(run, delay);
    };

    timerId = setTimeout(run, delay);
    return () => {
      timerId && clearTimeout(timerId);
    };
  }, [delay]);

这里是demouseRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue),返回的 ref 对象在组件的整个生命周期内保持不变。我们在 Hooks FAQ可以看到更多关于 useRef 的理解。即「ref」对象是一个 current 属性可变且可以容纳任意值的通用容器。我们还需要在 run 函数中加入

timerId = setTimeout(run, delay);

从而模拟 setInterval

3. 优化

我们要做到适时触发,即点击之后再开启定时器。这里是demo

我们通过使用参数 delay 是否为 null 来判断是否开启定时器。

useEffect(() => {
+    if (delay === null) {
+      return;
+    }
    let timerId = null;
    const run = () => {
      const returnValue = savedCallback.current();
      if (returnValue) {
        if (returnValue instanceof Function) {
          returnValue();
        } else {
          throw new Error("返回值必须是函数!");
        }
        timerId && clearTimeout(timerId);
        return;
      }
      timerId = setTimeout(run, delay);
    };

    timerId = setTimeout(run, delay);
    return () => {
      timerId && clearTimeout(timerId);
    };
  }, [delay]);

之后我们还可以设置 delay 从而控制定时器的 interval 间隔
最后再做一些类型检测即可

相关文章

  • React 自定义 Hooks 之 useSetInterval

    概述 在业务场景中,我们总会遇到倒计时。例如发送验证码之后的 60s 重新发送的倒计时。最近在使用 React H...

  • ahooks之useHistoryTravel详解

    前言 ahooks是阿里开源的一个react hooks 工具库,其中包含了许多业务上能用到的自定义hooks。本...

  • React Hooks

    React Hooks Hooks其实就是有状态的函数式组件。 React Hooks让React的成本降低了很多...

  • react-hooks

    前置 学习面试视频 总结react hooks react-hooks react-hooks为函数组件提供了一些...

  • React Hooks

    前言 React Conf 2018 上 React 提出了关于 React Hooks 的提案,Hooks 作为...

  • 5分钟简单了解React-Hooks

    首先附上官网正文?:React Hooks Hooks are a new addition in React 1...

  • react-hooks

    react-hooks react-hooks 是react16.8以后,react新增的钩子API,目的是增加代...

  • React Hooks的使用限制

    React Hooks的使用限制 只能用于函数组件或自定义Hooks中 不能写在条件语句中 不能写在函数组件或自定...

  • React-hooks API介绍

    react-hooks HOOKS hooks概念在React Conf 2018被提出来,并将在未来的版本中被...

  • React Hooks 入门

    React Hooks 是 React v16.8 版本引入了全新的 API。 React Hooks 基本概念 ...

网友评论

      本文标题:React 自定义 Hooks 之 useSetInterval

      本文链接:https://www.haomeiwen.com/subject/xcsdwhtx.html