增强的Effect(静态锁),简单的倒计时,你还不用?

1,507 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

大家好,我是小杜杜,有的时候我们要做活动的时候常常会有倒计时的功能,或者是一些常见的副作用,节流或防抖,而这些副作用可以通过一个简单的Hook就能实现,接下来将逐渐讲解

学习ahooks需要你抱着这样的态度去学习:你可以不会,但你不能不知道,万一哪天用到了呢

本文介绍:setInterval、setInterval、useCountDown、useDebounce、useDebounceFn、useThrottle、useThrottleFn、useLockFn 9个Hook

跟之前一样我们通过 ⭐️ 做个标记,说明钩子的重要性

⭐️:一般; ⭐️⭐️:重要; ⭐️⭐️⭐️:很重要

在所介绍的这些钩子中,尤其注意 useReactive 这个钩子,相信他能给予你很多帮助(● ̄(エ) ̄●)

演示示例: Domesy/ahook

0a3c524f2805476ab2e694aeb035ec57_tplv-k3u1fbpfcp-watermark.webp

useInterval 管理 setInterval 的函数

推荐指数 ⭐️⭐️ useInterval:简单的理解就是,每隔一段时间就执行的函数,类似于轮询

  • fn:重复调用的函数
  • delay:间隔时间,当值为 null 或 undefined 会停止计时器
  • option:对象,包含 immediate(是否在首次进行渲染,默认为false)

代码示例

  import React, { useState } from 'react';
  import { Button } from 'antd';
  import { useInterval } from 'ahooks';

  const Test: React.FC<any> = () => {
    const [count, setCount] = useState(0);

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

    return (
      <>
        <div style={{fontWeight: 'bolder'}}>基础用法:</div>
        <div style={{marginTop: 8}}>每隔1000ms,数字加 1: {count}</div>
      </>
    );
  }

  const Mock: React.FC<any> = () => {
    const [count, setCount] = useState(0);
    const [interval, setInterval] = useState<number | null>(1000);

    useInterval(
      () => {
        setCount(count + 1);
      },
      interval,
      { immediate: true },
    );


    return (
      <>
        <Test />
        <div style={{fontWeight: 'bolder'}}>高阶用法:</div>
        <div style={{marginTop: 8}}>每隔{interval || 0}ms,数字加 1: {count}</div>
        <div style={{marginTop: 8}}>相隔时间:{interval}</div>
        <div style={{marginTop: 8, display: 'flex', justifyContent: 'flex-start'}}>
          <Button type='primary' style={{marginRight: 16}} onClick={() => {
            if(typeof interval === 'number') setInterval(interval + 1000)
          }}>加1s</Button>
          <Button type='primary' style={{marginRight: 16}} onClick={() => setInterval(1000)}>重置</Button>
          <Button type='primary' style={{marginRight: 16}} onClick={() => setInterval(null)}>清除</Button>
        </div>
      </>
    );
  };

  export default Mock;

useTimeout 管理 setInterval 的函数

推荐指数 ⭐️⭐️

useTimeout:处理计时器函数的Hook

  • fn:重复调用的函数
  • delay:间隔时间,当值为 null 或 undefined 会停止计时器 代码示例:
import React, { useState } from 'react';
  import { Button } from 'antd';
  import { useTimeout } from 'ahooks';

  const Mock: React.FC<any> = () => {
    const [count, setCount] = useState(0);

    useTimeout(() => {
      setCount(count + 1);
    }, 2000);

    return (
      <div>2000ms 后数字加1: {count}</div>
    );
  };

  export default Mock;

useCountDown 倒计时

推荐指数 ⭐️⭐️⭐️

useCountDown

  • 首先有两个值,一个是目标值(targetDate),一个是设置值,如果不设置初始值(setTargetDate),则默认为现在
  • 会自动计算离目标值的天月周等(formattedRes),同时也有据目标值的时间戳(countdown),注意这里的单位是毫秒,所以精确到秒为 Math.round(countdown / 1000)
  • 有常用的倒计时功能,并能控制停止时所触发的函数(onEnd)

image.png

代码示例

 import React, { useEffect } from 'react';
  import { Button, message } from 'antd';
  import { useBoolean } from 'ahooks';
  import { Method } from '@/utils';


const CountDownTest1: React.FC<any> = () => {
  const [targetDate, setTargetDate] = useState<any>(Method.getDate({add: 3}));
  const [countdown, formattedRes] = useCountDown({
    targetDate,
  });

  useEffect(() => {
    setTargetDate(Method.getDate({add: 2}))
  }, [])

  const { days, hours, minutes, seconds, milliseconds } = formattedRes;

  return <div>设置时间为后天,目标时间为大后天:{days} 天, {hours} 小时 {minutes} 分钟 {seconds} 秒 {milliseconds}</div>
}

const CountDownTest2: React.FC<any> = () => {
  const [targetDate, setTargetDate] = useState<any>(Method.getDate({add: 3}));

  const [countdown, formattedRes] = useCountDown({
    targetDate,
    onEnd: () => {
      message.info('已停止!')
    }
  });

  return <div style={{display: 'flex',justifyContent: 'flex-start'}}>
    倒计时:
    <Button style={{margin: '0 24px'}} type='primary' onClick={() => {
      setTargetDate(Date.now() + 60000);
    }}>{countdown === 0 ? '倒计时' : “Math.round(countdown / 1000)}s“</Button>
    <Button onClick={() => {
      setTargetDate(undefined);
    }}>停止 </Button>
  </div>
}
const CountDownTest1: React.FC<any> = () => {
  const [targetDate, setTargetDate] = useState<any>(Method.getDate({add: 3}));
  const [countdown, formattedRes] = useCountDown({
    targetDate,
  });

  useEffect(() => {
    setTargetDate(Method.getDate({add: 2}))
  }, [])

  const { days, hours, minutes, seconds, milliseconds } = formattedRes;

  return <div>设置时间为后天,目标时间为大后天:{days} 天, {hours} 小时 {minutes} 分钟 {seconds} 秒 {milliseconds}</div>
}

const CountDownTest2: React.FC<any> = () => {
  const [targetDate, setTargetDate] = useState<any>(Method.getDate({add: 3}));

  const [countdown, formattedRes] = useCountDown({
    targetDate,
    onEnd: () => {
      message.info('已停止!')
    }
  });

  return <div style={{display: 'flex',justifyContent: 'flex-start'}}>
    倒计时:
    <Button style={{margin: '0 24px'}} type='primary' onClick={() => {
      setTargetDate(Date.now() + 60000);
    }}>{countdown === 0 ? '倒计时' : Math.round(countdown / 1000)}s</Button>
    <Button onClick={() => {
      setTargetDate(undefined);
    }}>停止 </Button>
  </div>
}

  const Mock: React.FC<any> = () => {
    const [countdown, formattedRes] = useCountDown({
      targetDate: Method.getDate({add: 1}),
    });

    const { days, hours, minutes, seconds, milliseconds } = formattedRes;

    return (
      <>
        <div>距离下一天0点的数据:{days} 天, {hours} 小时 {minutes} 分钟 {seconds} 秒 {milliseconds}</div>
        <div></div>
        <CountDownTest1 />
        <div></div>
        <CountDownTest2 />
      </>
    };

  export default Mock;
              

useDebounce 对值的防抖

推荐指数 ⭐️

使用:const debouncedValue = useDebounce(value: any, { wait: number}])

value: 防抖的值, wait:超时时间

代码示例

  import React, { useState } from 'react';
  import { Button } from 'antd';
  import { useDebounce } from 'ahooks';

  const Mock: React.FC<any> = () => {
    const [value, setValue] = useState<string>('');
    const debouncedValue = useDebounce(value, { wait: 500 });

    return (
      <div>
        <input
          value={value}
          onChange={(e) => setValue(e.target.value)}
          placeholder="Typed value"
          style={{ width: 280 }}
        />
        <p style={{ marginTop: 16 }}>500ms后才会变化: {debouncedValue}</p>
      </div>
    );
  };

  export default Mock;

useDebounceFn 对函数的防抖

推荐指数 ⭐️

useDebounceFn:对函数进行防抖的hook,频繁调用run方法,但只会执行最后一遍

使用方法:const { run, cancel, flush} = useDebounceFn(fn: (...args:any) => any, { wait: number})

run(防抖进行的函数)、cancel(取消当前防抖)、flush(当前防抖立即调用)

代码示例

import React, { useState } from 'react';
  import { Button } from 'antd';
  import { useDebounceFn } from 'ahooks';

  const Mock: React.FC<any> = () => {
    const [value, setValue] = useState(0);
    const { run } = useDebounceFn(
      () => {
        setValue(value + 1);
      },
      {
        wait: 500,
      },
    );

    return (
      <div>
        <p style={{ marginTop: 16 }}> 有效点击次数: {value} </p>
        <Button type="primary" onClick={() => {run()}}>快速点击</Button>
      </div>
    );
  };

  export default Mock;

useThrottle 对值的节流

推荐指数 ⭐️

useThrottle:对值进行节流的hook频繁调用run方法,但只会执行最后一遍

使用方法: const throttledValue = useThrottle(value: any, { wait: number})

代码示例:

import React, { useState } from 'react';
  import { Button } from 'antd';
  import { useThrottle } from 'ahooks';

  const Mock: React.FC<any> = () => {
    const [value, setValue] = useState<string>();
    const throttledValue = useThrottle(value, { wait: 500 });

    return (
      <div>
        <input
          value={value}
          onChange={(e) => setValue(e.target.value)}
          placeholder="输入值"
          style={{ width: 280 }}
        />
        <p style={{ marginTop: 16 }}>每隔 500ms 变化一次: {throttledValue}</p>
      </div>
    );
  };

  export default Mock;

useThrottleFn 对函数的节流

推荐指数 ⭐️

useThrottleFn:对函数进行节流的hook,频繁调用 run,但只会每隔 500ms 执行一次相关函数。

使用方法: const { run, cancel, flush} = useThrottleFn(fn: (...args:any) => any, { wait: number})

run(节流进行的函数)、cancel(取消当前节流)、flush(当前节流立即调用)

代码示例

import React, { useState } from 'react';
  import { Button } from 'antd';
  import { useThrottleFn } from 'ahooks';

  const Mock: React.FC<any> = () => {
    const [value, setValue] = useState(0);
    const { run } = useThrottleFn(
      () => {
        setValue(value + 1);
      },
      { wait: 500 },
    );

    return (
      <div>
        <p style={{ marginBottom: 16 }}> Clicked count: {value} </p>
        <Button type='primary' onClick={run}>快速点击</Button>
      </div>
    );
  };

  export default Mock;

useLockFn 静态锁

推荐指数 ⭐️⭐️⭐️

useLockFn:用于给一个异步函数增加竞态锁,防止并发执行。

我们在提交表单,订单的时候有可能会因为用户的误操作等进行重复提交,这是有的小伙伴说使用 防抖,其实防抖并不是最佳的选择,最好的方式就是静态锁,如果当前的方法不执行完,则不会再次有效,而不是像防抖一样有个几秒内的时间,所以这个钩子非常重要

image.png

代码示例

import React, { useState } from 'react';
  import { Button } from 'antd';
  import { useLockFn } from 'ahooks';

  function mockApiRequest() {
    return new Promise((resolve:any) => {
      setTimeout(() => {
        resolve();
      }, 2000);
    });
  }

  const Mock: React.FC<any> = () => {
    const [count, setCount] = useState(0);

    const submit = useLockFn(async () => {
      message.info('开始执行')
      await mockApiRequest();
      setCount((v) => v + 1)
      message.info('执行结束')
    })

    return (
      <>
        <div>点击次数:{count}</div>
        <Button style={{marginTop: 8}} type='primary' onClick={submit} >点击</Button>
      </>
    );
  };

  export default Mock;