一些常用的react组件封装实现示例--1

一些常用的react组件封装实现示例--1

这些组件的实现可以帮助那些想要学习 react 并熟练 react 语法的人学习。

手风琴组件

实现一个具有多个可折叠内容元素的手风琴菜单,实现思路如下:

  • 定义一个 AccordionItem 组件,它呈现一个 <div>。 按钮更新组件并通过 handleClick 回调通知其父级。
  • 使用 AccordionItem 中的 isCollapsed 属性确定其外观并设置其类名。
  • 定义 Accordion 组件。 使用 useState() 挂钩将 bindIndex 状态变量的值初始化为 defaultIndex。
  • 通过识别函数的名称过滤子项以删除除 AccordionItem 之外的不必要节点。
  • 在收集的节点上使用 Array.prototype.map() 来呈现各个可折叠元素。
  • 定义 changeItem,当点击 AccordionItem 的 <div> 时执行。
  • changeItem 执行传递的回调 onItemClick,并根据单击的元素更新 bindIndex。

tsx 代码如下所示:

tsx 复制代码
import { cx, css } from '@emotion/css';
import React, { useState } from 'react';
import type { ReactNode, SyntheticEvent } from 'react';

const baseStyle = css`
  line-height: 1.5715;
`;
const AccordionContainer = cx(
  baseStyle,
  css`
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    color: #000000d9;
    font-size: 14px;
    background-color: #fafafa;
    border: 1px solid #d9d9d9;
    border-bottom: 0;
    border-radius: 2px;
  `
);
const AccordionItemContainer = css`
  border-bottom: 1px solid #d9d9d9;
`;
const AccordionItemHeader = cx(
  baseStyle,
  css`
    position: relative;
    display: flex;
    flex-wrap: nowrap;
    align-items: flex-start;
    padding: 12px 16px;
    color: rgba(0, 0, 0, 0.85);
    cursor: pointer;
    transition: all 0.3s, visibility 0s;
    box-sizing: border-box;
  `
);

const AccordionItemContent = css`
  color: #000000d9;
  background-color: #fff;
  border-top: 1px solid #d9d9d9;
  transition: all 0.3s ease-in-out;
  padding: 16px;
  &.collapsed {
    display: none;
  }
  &.expanded {
    display: block;
  }
`;

interface AccordionItemType {
  index: string | number;
  label: string;
  isCollapsed: boolean;
  handleClick(e: SyntheticEvent): void;
  children: ReactNode;
}
interface AccordionType {
  defaultIndex: number | string;
  onItemClick(key: number | string): void;
  children: JSX.Element[];
}

const AccordionItem = (props: Partial<AccordionItemType>) => {
  const { label, isCollapsed, handleClick, children } = props;
  return (
    <div className={AccordionItemContainer} onClick={handleClick}>
      <div className={AccordionItemHeader}>{label}</div>
      <div
        aria-expanded={isCollapsed}
        className={`${AccordionItemContent}${
          isCollapsed ? ' collapsed' : ' expanded'
        }`}
      >
        {children}
      </div>
    </div>
  );
};
// 设置一个函数名用作判断,生产环境无法使用function.name
AccordionItem.displayName = 'AccordionItem';

const Accordion = (props: Partial<AccordionType>) => {
  const { defaultIndex, onItemClick, children } = props;
  const [bindIndex, setBindIndex] = useState(defaultIndex);
  const changeItem = (index: number | string) => {
    if (typeof onItemClick === 'function') {
      onItemClick(index);
    }
    if (index !== bindIndex) {
      setBindIndex(index);
    }
  };
  const items = children?.filter(
    item => item?.type?.displayName === 'AccordionItem'
  );
  return (
    <div className={AccordionContainer}>
      {items?.map(({ props: { index, label, children } }) => (
        <AccordionItem
          key={index}
          label={label}
          children={children}
          isCollapsed={bindIndex !== index}
          handleClick={() => changeItem(index)}
        />
      ))}
    </div>
  );
};

Accordion.AccordionItem = AccordionItem;
export default Accordion;

jsx 代码如下所示:

jsx 复制代码
import { cx, css } from '@emotion/css';
import React, { useState } from 'react';

const baseStyle = css`
  line-height: 1.5715;
`;
const AccordionContainer = cx(
  baseStyle,
  css`
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    color: #000000d9;
    font-size: 14px;
    background-color: #fafafa;
    border: 1px solid #d9d9d9;
    border-bottom: 0;
    border-radius: 2px;
  `
);
const AccordionItemContainer = css`
  border-bottom: 1px solid #d9d9d9;
`;
const AccordionItemHeader = cx(
  baseStyle,
  css`
    position: relative;
    display: flex;
    flex-wrap: nowrap;
    align-items: flex-start;
    padding: 12px 16px;
    color: rgba(0, 0, 0, 0.85);
    cursor: pointer;
    transition: all 0.3s, visibility 0s;
    box-sizing: border-box;
  `
);

const AccordionItemContent = css`
  color: #000000d9;
  background-color: #fff;
  border-top: 1px solid #d9d9d9;
  transition: all 0.3s ease-in-out;
  padding: 16px;
  &.collapsed {
    display: none;
  }
  &.expanded {
    display: block;
  }
`;

const AccordionItem = props => {
  const { label, isCollapsed, handleClick, children } = props;
  return (
    <div className={AccordionItemContainer} onClick={handleClick}>
      <div className={AccordionItemHeader}>{label}</div>
      <div
        aria-expanded={isCollapsed}
        className={`${AccordionItemContent}${
          isCollapsed ? ' collapsed' : ' expanded'
        }`}
      >
        {children}
      </div>
    </div>
  );
};
// 设置一个函数名用作判断,生产环境无法使用function.name
AccordionItem.displayName = 'AccordionItem';

const Accordion = props => {
  const { defaultIndex, onItemClick, children } = props;
  const [bindIndex, setBindIndex] = useState(defaultIndex);
  const changeItem = index => {
    if (typeof onItemClick === 'function') {
      onItemClick(index);
    }
    if (index !== bindIndex) {
      setBindIndex(index);
    }
  };
  const items = children?.filter(
    item => item?.type?.displayName === 'AccordionItem'
  );
  return (
    <div className={AccordionContainer}>
      {items?.map(({ props: { index, label, children } }) => (
        <AccordionItem
          key={index}
          label={label}
          children={children}
          isCollapsed={bindIndex !== index}
          handleClick={() => changeItem(index)}
        />
      ))}
    </div>
  );
};

Accordion.AccordionItem = AccordionItem;
export default Accordion;

参考在线示例

特别说明: 该组件的实现依赖@emotion 和 react。

提示组件

实现一个带有 prop 类型的提示组件,实现思路如下:

  • 使用 useState() 钩子创建 isShown 和 isLeaving 状态变量,并最初将两者都设置为 false。
  • 定义 timeoutId 以保留计时器实例以在组件卸载时清除。
  • 卸载组件时,使用 useEffect() 钩子将 isShown 的值更新为 true,并使用 timeoutId 清除间隔。
  • 定义一个 closeAlert 函数,通过显示淡出动画将组件设置为从 DOM 中移除,并通过 setTimeout() 将 isShown 设置为 false。

less 代码如下:

less 复制代码
@keyframes leave {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@baseSelector: alert;
@infoBgColor: #e6f7ff;
@infoBorderColor: #91d5ff;
@warningBgColor: #fffbe6;
@warningBorderColor: #ffe58f;
@errorBgColor: #fff2f0;
@errorBorderColor: #ffccc7;
@successBgColor: #f6ffed;
@successBorderColor: #b7eb8f;
@color: rgba(0, 0, 0, 0.85);
@closeActiveColor: fadeout(@color, 25%);
.@{baseSelector} {
  box-sizing: border-box;
  margin: 0;
  color: @color;
  font-size: 14px;
  line-height: 1.5715;
  list-style: none;
  position: relative;
  display: flex;
  align-items: center;
  padding: 8px 15px;
  word-wrap: break-word;
  border-radius: 2px;
  border: 1px solid transparent;
  &.@{baseSelector}-block {
    width: percentage(1);
  }
  &.@{baseSelector}-info {
    background-color: @infoBgColor;
    border-color: @infoBorderColor;
  }

  &.@{baseSelector}-warning {
    background-color: @warningBgColor;
    border-color: @warningBorderColor;
  }

  &.@{baseSelector}-error {
    background-color: @errorBgColor;
    border-color: @errorBorderColor;
  }

  &.@{baseSelector}-success {
    background-color: @successBgColor;
    border-color: @successBorderColor;
  }

  .@{baseSelector}-close {
    margin-left: 8px;
    padding: 0;
    overflow: hidden;
    font-size: 16px;
    line-height: 12px;
    background-color: transparent;
    border: none;
    outline: none;
    cursor: pointer;
    margin: 0;
    color: @color;
    position: absolute;
    right: 10px;
    top: 10px;
    &:hover {
      color: @closeActiveColor;
    }
  }
}

tsx 代码如下:

tsx 复制代码
import React, { useState, useEffect } from 'react';
import './alert.less';

interface AlertPropType {
  isDefaultShown: boolean;
  timeout: number;
  type: string;
  message: string;
  showClose: boolean;
  block: boolean;
}

const Alert = (props: Partial<AlertPropType>) => {
  const {
    isDefaultShown,
    timeout = 250,
    type,
    message,
    showClose,
    block
  } = props;
  const [isShown, setIsShown] = useState(isDefaultShown);
  const [isLeaving, setIsLeaving] = useState(false);

  let timer: number | null = null;
  useEffect(() => {
    setIsShown(true);
    return () => {
      if (timer) {
        clearTimeout(timer);
      }
    };
  }, [timeout, timer, isDefaultShown]);

  const closeAlert = () => {
    setIsLeaving(true);
    timer = setTimeout(() => {
      setIsLeaving(false);
      setIsShown(false);
    }, timeout);
  };

  return isShown ? (
    <div
      className={`alert ${'alert-' + type}${isLeaving ? ' leaving' : ''}${
        block ? ' alert-block' : ''
      }`}
      role="alert"
    >
      <button
        className="alert-close"
        onClick={closeAlert}
        style={{ display: showClose ? 'block' : 'none' }}
      >
        &times;
      </button>
      {message}
    </div>
  ) : null;
};

export default Alert;

jsx 代码如下:

jsx 复制代码
import React, { useState, useEffect } from 'react';
import './alert.less';

const Alert = props => {
  const {
    isDefaultShown,
    timeout = 250,
    type,
    message,
    showClose,
    block
  } = props;
  const [isShown, setIsShown] = useState(isDefaultShown);
  const [isLeaving, setIsLeaving] = useState(false);

  let timer = null;
  useEffect(() => {
    setIsShown(true);
    return () => {
      if (timer) {
        clearTimeout(timer);
      }
    };
  }, [timeout, timer, isDefaultShown]);

  const closeAlert = () => {
    setIsLeaving(true);
    timer = setTimeout(() => {
      setIsLeaving(false);
      setIsShown(false);
    }, timeout);
  };

  return isShown ? (
    <div
      className={`alert ${'alert-' + type}${isLeaving ? ' leaving' : ''}${
        block ? ' alert-block' : ''
      }`}
      role="alert"
    >
      <button
        className="alert-close"
        onClick={closeAlert}
        style={{ display: showClose ? 'block' : 'none' }}
      >
        &times;
      </button>
      {message}
    </div>
  ) : null;
};

export default Alert;

参考在线示例

特别说明: 该组件的实现依赖 react 和 less。

自动文本链接组件

实现一个将字符串呈现为纯文本,并将 URL 转换为适当的链接元素的组件,实现思路如下所示:

  • 使用带有正则表达式的 String.prototype.split() 和 String.prototype.match() 来查找字符串中的 URL。
  • 返回呈现为 <a> 元素的匹配 URL,如有必要,处理缺少的协议前缀。
  • 将字符串的其余部分呈现为纯文本。

tsx 代码如下所示:

tsx 复制代码
import React from 'react';
import styled from '@emotion/styled';

const LinkContainer = styled.span`
  display: inline-block;
  a {
    line-height: 1.5715;
    position: relative;
    display: inline-block;
    font-weight: 400;
    white-space: nowrap;
    text-align: center;
    cursor: pointer;
    transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
    user-select: none;
    touch-action: manipulation;
    height: 32px;
    padding: 4px 15px;
    font-size: 14px;
    border-radius: 2px;
    color: #1890ff;
    background: transparent;
    text-decoration: none;
    &:hover {
      color: #40a9ff;
    }
  }
`;

interface AutoLinkPropType {
  text: string;
}
const AutoLink = (props: Partial<AutoLinkPropType>) => {
  const { text } = props;
  const delimiter =
    /((?:https?:\/\/)?(?:(?:[a-z0-9]?(?:[a-z0-9\-]{1,61}[a-z0-9])?\.[^\.|\s])+[a-z\.]*[a-z]+|(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(?::\d{1,5})*[a-z0-9.,_\/~#&=;%+?\-\\(\\)]*)/gi;
  return (
    <LinkContainer>
      {text?.split(delimiter).map(word => {
        const match = word.match(delimiter);
        if (match) {
          const url = match[0];
          return (
            <a
              href={url.startsWith('http') ? url : `http://${url}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              {url}
            </a>
          );
        }
        return word;
      })}
    </LinkContainer>
  );
};
export default AutoLink;

jsx 代码如下所示:

jsx 复制代码
import React from 'react';
import styled from '@emotion/styled';

const LinkContainer = styled.span`
  display: inline-block;
  a {
    line-height: 1.5715;
    position: relative;
    display: inline-block;
    font-weight: 400;
    white-space: nowrap;
    text-align: center;
    cursor: pointer;
    transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
    user-select: none;
    touch-action: manipulation;
    height: 32px;
    padding: 4px 15px;
    font-size: 14px;
    border-radius: 2px;
    color: #1890ff;
    background: transparent;
    text-decoration: none;
    &:hover {
      color: #40a9ff;
    }
  }
`;

const AutoLink = props => {
  const { text } = props;
  const delimiter =
    /((?:https?:\/\/)?(?:(?:[a-z0-9]?(?:[a-z0-9\-]{1,61}[a-z0-9])?\.[^\.|\s])+[a-z\.]*[a-z]+|(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(?::\d{1,5})*[a-z0-9.,_\/~#&=;%+?\-\\(\\)]*)/gi;
  return (
    <LinkContainer>
      {text?.split(delimiter).map(word => {
        const match = word.match(delimiter);
        if (match) {
          const url = match[0];
          return (
            <a
              href={url.startsWith('http') ? url : `http://${url}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              {url}
            </a>
          );
        }
        return word;
      })}
    </LinkContainer>
  );
};

export default AutoLink;

参考在线示例

特别说明: 该组件的实现依赖@emotion 和 react。

受控的输入框组件

实现一个受控的 <input> 元素,该元素使用回调函数通知其父级有关值的更新,实现思路如下所示:

  • 使用从父级传下来的值作为受控输入字段的值。
  • 使用 onChange 事件触发 onChange 回调并将新值发送给父级。
  • 父级必须更新输入字段的 value 属性,以便其值在用户输入时更改。

tsx 代码如下所示:

tsx 复制代码
import styled from '@emotion/styled';
import React from 'react';
import type { SyntheticEvent } from 'react';

const StyleInput = styled.input`
  box-sizing: border-box;
  margin: 0;
  font-variant: tabular-nums;
  list-style: none;
  font-feature-settings: 'tnum';
  position: relative;
  display: inline-block;
  width: 100%;
  min-width: 0;
  padding: 4px 11px;
  color: #000000d9;
  font-size: 14px;
  line-height: 1.5715;
  background-color: #fff;
  background-image: none;
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  transition: all 0.3s;
  &:focus {
    border-color: #40a9ff;
    box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
    border-right-width: 1px;
    outline: 0;
  }
`;
type LiteralUnion<T extends U, U> = T & (U & {});
interface ControlledInputProps {
  type: LiteralUnion<
    | 'button'
    | 'checkbox'
    | 'color'
    | 'date'
    | 'datetime-local'
    | 'email'
    | 'file'
    | 'hidden'
    | 'image'
    | 'month'
    | 'number'
    | 'password'
    | 'radio'
    | 'range'
    | 'reset'
    | 'search'
    | 'submit'
    | 'tel'
    | 'text'
    | 'time'
    | 'url'
    | 'week',
    string
  >;
  value: string;
  onChange(v: string): void;
  placeholder: string;
}
const ControlledInput = (props: Partial<ControlledInputProps>) => {
  const { value, onChange, ...rest } = props;
  const onChangeHandler = (e: SyntheticEvent) => {
    if (onChange) {
      onChange((e.target as HTMLInputElement).value);
    }
  };
  return (
    <StyleInput value={value} onChange={onChangeHandler} {...rest}></StyleInput>
  );
};

jsx 代码如下所示:

jsx 复制代码
import styled from '@emotion/styled';
import React from 'react';

const StyleInput = styled.input`
  box-sizing: border-box;
  margin: 0;
  font-variant: tabular-nums;
  list-style: none;
  font-feature-settings: 'tnum';
  position: relative;
  display: inline-block;
  width: 100%;
  min-width: 0;
  padding: 4px 11px;
  color: #000000d9;
  font-size: 14px;
  line-height: 1.5715;
  background-color: #fff;
  background-image: none;
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  transition: all 0.3s;
  &:focus {
    border-color: #40a9ff;
    box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
    border-right-width: 1px;
    outline: 0;
  }
`;

const ControlledInput = props => {
  const { value, onChange, ...rest } = props;
  const onChangeHandler = e => {
    onChange?.(e.target.value);
  };
  return (
    <StyleInput value={value} onChange={onChangeHandler} {...rest}></StyleInput>
  );
};

export default ControlledInput;

参考在线示例

特别说明: 该组件的实现依赖@emotion 和 react。

倒计时组件

实现一个倒计时组件,实现思路如下所示:

  • 使用 useState() 钩子创建一个状态变量来保存时间值。 从 props 初始化它并将其解构为它的组件。
  • 使用 useState() 钩子创建暂停和结束状态变量,用于防止计时器在暂停或时间用完时滴答作响。
  • 创建一个方法 tick,它根据当前值更新时间值(即将时间减少一秒)。
  • 创建一个方法 reset,将所有状态变量重置为其初始状态。
  • 使用 useEffect() 钩子通过 setTimeout()结合递归 每秒调用一次 tick 方法,并在组件卸载时使用 clearTimeout() 进行清理。
  • 使用 String.prototype.padStart() 将时间数组的每个部分填充为两个字符,以创建计时器的可视化表示。

helper.ts 代码如下所示:

ts 复制代码
import type { MutableRefObject, DependencyList, Ref } from 'react';
import { useImperativeHandle, useEffect, useRef } from 'react';

export const useChildrenHandler = <T, K extends object>(
  originRef: MutableRefObject<T>,
  handler: K,
  deps?: DependencyList
): void =>
  useImperativeHandle(
    originRef,
    () => {
      return {
        ...originRef.current,
        ...handler
      };
    },
    deps
  );

export type ImperFunc = (...args: any[]) => any;
export type ImperRef = Record<string, ImperFunc>;
export type ImperItem = {
  ref: Ref<ImperRef>;
};
let timer: number;
export const useTimeout = (callback, delay = 1000) => {
  const ref = useRef() as MutableRefObject<() => void>;
  useEffect(() => {
    ref.current = callback;
  });
  useEffect(() => {
    const handler = () => {
      ref.current();
      timer = setTimeout(handler, delay);
    };
    handler();
    return () => clearTimeout(timer);
  }, [delay]);
};

CountDown.tsx 代码如下所示:

tsx 复制代码
import React, { forwardRef, useState, useCallback } from 'react';
import type { MutableRefObject } from 'react';
import styled from '@emotion/styled';
import type { ImperItem, ImperRef } from './helper';
import { useChildrenHandler, useTimeout } from './helper';

interface CountDownProps {
  hours: number;
  minutes: number;
  seconds: number;
  overText?: string;
  pausedText?: string;
  delimiter?: string;
}

const StyleCountdown = styled.div`
  color: rgba(0, 0, 0.85);
  margin-bottom: 10px;
  font-size: 16px;
`;

const CountDown = forwardRef(
  (props: Partial<CountDownProps>, ref: ImperItem['ref']) => {
    const {
      hours,
      minutes,
      seconds,
      overText = 'Time is up!',
      pausedText = 'paused!',
      delimiter = ':'
    } = props;
    const [paused, setPaused] = useState(false);
    const [over, setOver] = useState(false);
    const [[h = 0, m = 0, s = 0], setTime] = useState([
      hours,
      minutes,
      seconds
    ]);

    const tick = () => {
      if (paused || over) {
        return;
      }
      let newH = h,
        newM = m,
        newS = s;
      if (h === 0 && m === 0 && s === 0) {
        setOver(true);
      }

      if (m === 0 && s === 0) {
        newH--;
        newM = 59;
        newS = 59;
      }

      if (s === 0) {
        newM--;
        newS = 60;
      }
      newS--;
      setTime([newH, newM, newS]);
    };
    useTimeout(tick, 1000);
    useChildrenHandler(ref as MutableRefObject<ImperRef>, {
      onPaused: useCallback((status: boolean) => {
        setPaused(!status);
      }, []),
      onOver: useCallback(() => {
        setPaused(false);
        setOver(true);
      }, []),
      onRestart: useCallback(() => {
        setTime([h, m, s]);
        setPaused(false);
        setOver(false);
      }, [])
    });
    const fillZero = (n: number) => n.toString().padStart(2, '0');
    return (
      <StyleCountdown>
        {over
          ? overText
          : paused
          ? pausedText
          : `${fillZero(h)}${delimiter}${fillZero(m)}${delimiter}${fillZero(
              s
            )}`}
      </StyleCountdown>
    );
  }
);

export default CountDown;

help.js 代码如下所示:

js 复制代码
import { useImperativeHandle, useEffect, useRef } from 'react';

export const useChildrenHandler = (originRef, handler, deps?) =>
  useImperativeHandle(
    originRef,
    () => ({
      ...originRef.current,
      ...handler
    }),
    deps
  );
let timer;
// see this:https://www.aaron-powell.com/posts/2019-09-23-recursive-settimeout-with-react-hooks/
export const useTimeout = (callback, delay = 1000) => {
  const ref = useRef();
  useEffect(() => {
    ref.current = callback;
  });
  useEffect(() => {
    const handler = () => {
      ref.current();
      timer = setTimeout(handler, delay);
    };
    handler();
    return () => clearTimeout(timer);
  }, [delay]);
};

CountDown.jsx 代码如下所示:

jsx 复制代码
import React, { forwardRef, useState, useCallback } from 'react';
import styled from '@emotion/styled';
import { useChildrenHandler, useTimeout } from './helper';

const StyleCountdown = styled.div`
  color: rgba(0, 0, 0.85);
  margin-bottom: 10px;
  font-size: 16px;
`;

const CountDown = forwardRef((props, ref) => {
  const {
    hours,
    minutes,
    seconds,
    overText = 'Time is up!',
    pausedText = 'paused!',
    delimiter = ':'
  } = props;
  const [paused, setPaused] = useState(false);
  const [over, setOver] = useState(false);
  const [[h = 0, m = 0, s = 0], setTime] = useState([hours, minutes, seconds]);

  const tick = () => {
    if (paused || over) {
      return;
    }
    let newH = h,
      newM = m,
      newS = s;
    if (h === 0 && m === 0 && s === 0) {
      setOver(true);
    }

    if (m === 0 && s === 0) {
      newH--;
      newM = 59;
      newS = 59;
    }

    if (s === 0) {
      newM--;
      newS = 60;
    }
    newS--;
    setTime([newH, newM, newS]);
  };
  useTimeout(tick, 1000);
  useChildrenHandler(ref, {
    onPaused: useCallback(status => {
      setPaused(!status);
    }, []),
    onOver: useCallback(() => {
      setPaused(false);
      setOver(true);
    }, []),
    onRestart: useCallback(() => {
      setTime([h, m, s]);
      setPaused(false);
      setOver(false);
    }, [])
  });
  const fillZero = n => n.toString().padStart(2, '0');
  return (
    <StyleCountdown>
      {over
        ? overText
        : paused
        ? pausedText
        : `${fillZero(h)}${delimiter}${fillZero(m)}${delimiter}${fillZero(s)}`}
    </StyleCountdown>
  );
});

export default CountDown;

参考在线示例

特别说明: 该组件的实现依赖@emotion 和 react。

最后

更多实现参考源码

相关推荐
如若12324 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~1 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语1 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport1 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg1 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww1 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254881 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
星就前端叭2 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234522 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成2 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript