⌈Vue3&React18⌋动手实践构建组件库之❣️Input组件❣️

136 阅读1分钟

预览

Input | Vue3xy (chengbotao.github.io)
Input | Reactxy (chengbotao.github.io)

介绍

输入字符框
支持前后添加自定义内容
支持添加 icon

Props 属性

支持原生 Input 属性

属性名属性类型说明默认值
model-value| v-modelstring | number绑定值-
disabledboolean设置 Input 禁用false
classNamestring自定义CSS类名-
sizedoneOf "sm" | "lg"设置 Input 尺寸-
iconIconProp设置 Input 后面图标-

Event 事件

事件名参数类型说明
inputMouseEvent在 Input 值改变时触发
focusMouseEventInput 获得焦点时触发
blurMouseEventInput 失去焦点时触发
changeMouseEvent仅当 modelValue 改变时且当输入框失去焦点或按 Enter 时触发

Slot 插槽

插槽名说明
prepend输入框前置内容
append输入框后置内容

呈现

Vue3 实现

<template>
    <div :class="classes">
        <div v-if="slots.prepend" key="prepend" class="xy-input-group-prepend">
            <slot name="prepend"></slot>
        </div>
        <div v-if="icon" key="icon" class="icon-wrapper">
            <xyIcon :icon="icon"></xyIcon>
        </div>
        <input class="xy-input-inner" v-bind="$attrs" :disabled=disabled @focus="handleFocus" @blur="handleBlur"
            @change="handleChange" @input="handleInput" :value="modelValue" />
        <div v-if="slots.append" key="append" class="xy-input-group-append">
            <slot name="append"></slot>
        </div>
    </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
    name: 'XyInput',
    inheritAttrs: false,
})
</script>

<script setup lang="ts">
import { useSlots } from "vue";
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import classNames from 'classnames';

type InputSize = 'lg' | 'sm';

interface InputProps {
    modelValue?: string | number;
    disabled?: boolean;
    sized?: InputSize;
    icon?: IconProp;
    className?: string;
}

interface InputEmits {
    (event: "input", payload: Event): void
    (event: "focus", payload: FocusEvent): void
    (event: "blur", payload: FocusEvent): void
    (event: "change", payload: Event): void
    (event: "update:modelValue", payload: string): void
}
// slots
const slots = useSlots();

// Props
const props = withDefaults(defineProps<InputProps>(), {
    modelValue: ""
});

// Emits
const emits = defineEmits<InputEmits>();

// computed
const classes = classNames('xy-input-wrapper', props.className, {
    [`xy-input-size-${props.sized}`]: props.sized,
    'is-disabled': props.disabled,
    'xy-input-group': slots?.prepend || slots?.append,
    'xy-input-group-append': !!slots?.append,
    'xy-input-group-prepend': !!slots?.prepend,
});

// methods
const handleInput = (event: Event) => {
    emits("input", event)
    emits("update:modelValue", (event.target as HTMLInputElement).value)
}
const handleFocus = (event: FocusEvent) => {
    emits("focus", event)
}
const handleBlur = (event: FocusEvent) => {
    emits("blur", event)
}
const handleChange = (event: Event) => {
    emits("change", event)
}

// public 方法
defineExpose({})
</script>

Vue3 单元测试

// todo

React 实现

import React, { ChangeEvent, FC, InputHTMLAttributes, ReactElement } from 'react';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import classNames from 'classnames';
import Icon from '../icon/icon';

export type InputSize = 'lg' | 'sm';

export interface InputProps extends InputHTMLAttributes<HTMLElement> {
  /** 设置 Input 禁用 */
  disabled?: boolean;
  /** 设置 Input 尺寸 */
  sized?: InputSize;
  /** 添加图标,在右侧悬浮添加一个图标,用于提示 */
  icon?: IconProp;
  /** 添加前缀 用于配置一些固定组合 */
  prepend?: string | ReactElement;
  /** 添加后缀 用于配置一些固定组合 */
  append?: string | ReactElement;
  /** 自定义 css 类名 */
  className?: string;
  onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
}

/**
 * Input 组件
 *
 */
export const Input: FC<InputProps> = (props) => {
  const { disabled, sized, icon, prepend, append, className, ...restProps } = props;
  const classes = classNames('xy-input-wrapper', className, {
    [`xy-input-size-${sized}`]: sized,
    'is-disabled': disabled,
    'xy-input-group': prepend || append,
    'xy-input-group-append': !!append,
    'xy-input-group-prepend': !!prepend,
  });

  // 受控组件 || 非受控组件
  const fixControlledValue = (value: unknown) => {
    if (typeof value === 'undefined' || value === null) {
      return '';
    }
    return value as string;
  };
  if ('value' in props) {
    delete restProps.defaultValue;
    restProps.value = fixControlledValue(props.value);
  }

  return (
    <div className={classes} style={props.style}>
      {prepend && <div className="xy-input-group-prepend">{prepend}</div>}
      {icon && (
        <div className="icon-wrapper">
          <Icon icon={icon} title={`title-${icon}`} />
        </div>
      )}
      <input className="xy-input-inner" disabled={disabled} {...restProps} />
      {append && <div className="xy-input-group-append">{append}</div>}
    </div>
  );
};

export default Input;

React 单元测试

// todo

后记

个人博客 | Botaoxy (chengbotao.github.io)

感谢阅读,敬请斧正!