React+TS前台项目实战(十九)-- 全局常用组件封装:带加载状态和清除等功能的Input组件实现

发布于:2024-06-30 ⋅ 阅读:(16) ⋅ 点赞:(0)


前言

今天我们来封装一个input输入框组件,并提供一些常用的功能,你可以选择不同的 尺寸添加前缀显示加载状态触发回调函数自定义样式 等等。这些功能在这个项目中已经足够了,无论你是一个经验丰富的开发者还是一个刚刚入门的新手,这篇文章都将提供有用的知识和实践经验,以帮助你在自己项目中封装输入框时更加高效


Input组件

1. 功能分析

(1)通过传入loading加载状态属性,当激活时会显示加载图标
(2)通过传入size属性, 可以有不同的大小:“default”、“small” 或 “large”
(3)提供onChange值发生变化回调函数;失去焦点onBlur回调函数;键盘回车onKeyUp回调函数;
(4)当输入框有值时,组件会显示一个清除按钮,用户可以通过点击按钮来清除输入框的值
(5)通过传入className属性, 可以自定义输入框的样式
(6)通过 elprops 属性将其他属性传递给底层的 input 元素

2. 代码+详细注释

// @/components/Input/index.tsx
import { FC, ReactNode, useCallback, useState, ChangeEventHandler, FocusEvent, ComponentPropsWithoutRef, KeyboardEventHandler } from "react";
import classNames from "classnames";
import { InputContainer } from "./styled";
import { ReactComponent as SearchIcon } from "./assets/search.svg";
import { ReactComponent as LoadingIcon } from "./assets/loading.svg";
import { ReactComponent as ClearIcon } from "./assets/clear.svg";
import SimpleButton from "@/components/SimpleButton";

// 组件的属性类型
type Props = Omit<ComponentPropsWithoutRef<"input">, "size"> & {
  // 按下回车的回调
  onEnter?: () => void;
  // 是否显示加载状态
  loading?: boolean;
  // 输入框大小
  size?: "default" | "small" | "large" | undefined;
  // 输入框前缀
  prefix?: boolean | ReactNode;
};

// 输入框组件
const Input: FC<Props> = ({ loading, size, onEnter, value: propsValue, onChange: propsOnChange, onKeyUp: propsOnKeyUp, onBlur: propsOnBlur, className, ...elprops }) => {
  // 输入框的值,通过状态管理
  const [value, setValue] = useState(propsValue);

  // 输入事件
  const handlerChange: ChangeEventHandler<HTMLInputElement> = useCallback(
    (event) => {
      // 如果正在加载,直接返回
      if (loading) {
        return;
      }
      // 更新状态和回调
      setValue(event.target.value);
      propsOnChange?.(event);
    },
    [propsOnChange, setValue, loading]
  );

  // 回车事件
  const onKeyUp: KeyboardEventHandler<HTMLInputElement> = useCallback(
    (event) => {
      // 如果是回车键,触发回调
      const isEnter = event.keyCode === 13;
      if (isEnter) {
        onEnter?.();
      }
      propsOnKeyUp?.(event);
    },
    [onEnter, propsOnKeyUp]
  );

  // 失去焦点事件
  const onBlur = useCallback(
    (event: FocusEvent<HTMLInputElement>) => {
      propsOnBlur?.(event);
    },
    [propsOnBlur]
  );

  return (
    <InputContainer className={classNames(className)} size={size}>
      {/* 加载状态图标 */}
      {loading ? <LoadingIcon className={classNames("input-loading-icon")} /> : <SearchIcon className={classNames("input-search-icon")} />}
      {/* 输入框 */}
      <input enterKeyHint="search" value={value} onChange={handlerChange} onKeyUp={onKeyUp} onBlur={onBlur} {...elprops} />
      {/* 清除按钮 */}
      {value && (
        <SimpleButton className={classNames("input-clear-icon")} title="clear" onClick={() => setValue("")}>
          <ClearIcon />
        </SimpleButton>
      )}
    </InputContainer>
  );
};

export default Input;
------------------------------------------------------------------------------
// @/components/Input/styled.tsx
import styled from "styled-components";
import variables from "@/styles/variables.module.scss";
type InputProps = {
  size: "default" | "small" | "large" | undefined;
};
export const InputContainer = styled.div<InputProps>`
  @keyframes rotate {
    100% {
      transform: rotate(360deg);
    }
  }
  position: relative;
  margin: 0 auto;
  width: 100%;
  height: ${({ size }) => {
    if (size === "default") return "var(--cd-input-height)";
    else if (size === "small") return "var(--cd-input-sm-height)";
    else return "var(--cd-input-large-height)";
  }};
  padding-right: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: white;
  border: 0 solid white;
  border-radius: 4px;
  input {
    position: relative;
    width: 100%;
    height: 100%;
    font-size: 14px;
    padding: 0 8px;
    background: #fff;
    color: #333;
    border: 0 solid #fff;
    border-radius: 5px;
    &:focus {
      color: #333;
      outline: none;
    }
    &::placeholder {
      color: #888;
    }
    @media (max-width: ${variables.mobileBreakPoint}) {
      font-size: 12px;
      width: 100%;
      padding-left: 6px;
      padding-right: 16px;
    }
  }
  .input-loading-icon,
  .input-search-icon {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    margin-left: 8px;
  }
  .input-loading-icon {
    animation: rotate 2s linear infinite;
  }
  .input-clear-icon {
    display: flex;
    align-items: center;
    flex-shrink: 0;
  }
`;

3. 使用方式

// 引入组件
import Input from '@/components/Input'
// 使用
const [loading, setLoading] = useState(false);
const [searchkeyword, setSearchkeyword] = useState("");
{/* 输入框值变化回调事件 */}
const onInputChange: ChangeEventHandler<HTMLInputElement> = (event) => {
  console.log("onInputChange", event.target.value);
  setSearchkeyword(event.target.value);
};
{/* 失焦回调事件 */}
const onInputBlur = () => {};
{/* 小尺寸,不带loading */}
<Input placeholder="请输入" size="small" />
{/* 标准尺寸,带loading */}
<Input placeholder="请输入" loading={loading} onChange={onInputChange} onBlur={onInputBlur} />
{/* 大尺寸,不带loading */}
<Input placeholder="请输入" size="large" />
{/* 带前缀 */}
<Input hasPrefix placeholder="请输入" loading={loading}} />
{/* 不带前缀 */}
<Input placeholder="请输入" loading={loading} onChange={onInputChange} onBlur={onInputBlur} />

4. 效果展示

(1)输入后,加载效果如下
注:如请求数据时添加加载状态,请求结束后取消加载状态

在这里插入图片描述

(2)点击清除按钮,清除数据效果

在这里插入图片描述

(3)三种尺寸显示如下

在这里插入图片描述
(4)带前缀 / 不带前缀效果
在这里插入图片描述


总结

下一篇讲【全局常用Search组件封装】。关注本栏目,将实时更新。