HLJ 发布于
2025-05-22 15:39:44
0阅读

Vue3+TypeScript组件开发案例

以下是一个使用 Vue 3 Composition API + TypeScript 开发组件的完整案例,我们将创建一个带自定义功能的按钮组件:

1. 创建组件文件:CustomButton.vue

<template>
  <button
    :class="buttonClasses"
    :disabled="disabled || isLoading"
    @click="handleClick"
  >
    <span v-if="isLoading" class="button__loader"></span>
    <span v-else class="button__content">
      <slot name="icon">
        <span v-if="icon" class="button__icon">{{ icon }}</span>
      </slot>
      <slot></slot>
    </span>
  </button>
</template>

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

// 定义 Props 类型
interface Props {
  buttonType?: 'primary' | 'secondary' | 'text'
  size?: 'small' | 'medium' | 'large'
  icon?: string
  disabled?: boolean
  isLoading?: boolean
}

// 定义 Emits 类型
interface Emits {
  (e: 'click', event: MouseEvent): void
}

// 声明 Props 和 Emits
const props = withDefaults(defineProps<Props>(), {
  buttonType: 'primary',
  size: 'medium',
  disabled: false,
  isLoading: false
})

const emit = defineEmits<Emits>()

// 计算类名
const buttonClasses = computed(() => [
  'button',
  `button--${props.buttonType}`,
  `button--${props.size}`,
  {
    'button--disabled': props.disabled,
    'button--loading': props.isLoading
  }
])

// 点击事件处理
const handleClick = (event: MouseEvent) => {
  if (!props.disabled && !props.isLoading) {
    emit('click', event)
  }
}
</script>

<style scoped lang="scss">
.button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;

  &--primary {
    background-color: #409eff;
    color: white;

    &:hover:not(.button--disabled) {
      background-color: #79bbff;
    }
  }

  &--secondary {
    background-color: #e6e6e6;
    color: #333;

    &:hover:not(.button--disabled) {
      background-color: #d4d4d4;
    }
  }

  &--text {
    background: none;
    color: #409eff;

    &:hover:not(.button--disabled) {
      color: #79bbff;
    }
  }

  &--small {
    padding: 6px 12px;
    font-size: 12px;
  }

  &--medium {
    padding: 8px 16px;
    font-size: 14px;
  }

  &--large {
    padding: 12px 24px;
    font-size: 16px;
  }

  &--disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }

  &__content {
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }

  &__icon {
    display: inline-flex;
    align-items: center;
  }

  &__loader {
    width: 16px;
    height: 16px;
    border: 2px solid #f3f3f3;
    border-top: 2px solid #3498db;
    border-radius: 50%;
    animation: spin 1s linear infinite;
  }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>

2. 组件功能说明

  1. Props 类型定义

    • buttonType: 按钮类型(primary/secondary/text)
    • size: 尺寸(small/medium/large)
    • icon: 图标(支持 Unicode 字符或简写)
    • disabled: 禁用状态
    • isLoading: 加载状态
  2. 功能特性

    • 支持自定义图标插槽
    • 加载状态显示动画
    • 自动禁用按钮在加载状态
    • 类型安全的 TypeScript 支持
    • 样式隔离(scoped CSS)

3. 使用示例

<template>
  <div class="container">
    <CustomButton @click="handleClick">默认按钮</CustomButton>
    
    <CustomButton
      button-type="secondary"
      size="large"
      icon="★"
      @click="handleIconClick"
    >
      带图标按钮
    </CustomButton>

    <CustomButton
      :is-loading="loading"
      @click="handleAsyncClick"
    >
      异步操作按钮
    </CustomButton>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import CustomButton from './CustomButton.vue'

const loading = ref(false)

const handleClick = (event: MouseEvent) => {
  console.log('按钮点击', event)
}

const handleIconClick = () => {
  console.log('带图标按钮点击')
}

const handleAsyncClick = async () => {
  loading.value = true
  // 模拟异步操作
  await new Promise(resolve => setTimeout(resolve, 2000))
  loading.value = false
}
</script>

4. TypeScript 类型支持的优势

  1. Props 类型校验

    • 自动验证传入的 props 类型
    • 代码提示和自动补全
  2. Emit 事件类型安全

    • 确保触发的事件类型正确
    • 事件参数类型校验
  3. 更好的代码维护性

    • 明确的接口定义
    • 编译时类型检查

5. 组件开发最佳实践

  1. 组合式函数复用

    // useButton.ts
    import { computed } from 'vue'
    
    export function useButton(props: { buttonType: string; size: string }) {
      const buttonClasses = computed(() => [
        'button',
        `button--${props.buttonType}`,
        `button--${props.size}`
      ])
    
      return { buttonClasses }
    }
    
  2. 单元测试(使用 Vitest):

    import { mount } from '@vue/test-utils'
    import CustomButton from './CustomButton.vue'
    
    describe('CustomButton', () => {
      it('emits click event', async () => {
        const wrapper = mount(CustomButton)
        await wrapper.trigger('click')
        expect(wrapper.emitted()).toHaveProperty('click')
      })
    })
    

这个案例演示了:

  • Composition API 的使用
  • TypeScript 类型系统集成
  • 组件 Props 的类型定义
  • 自定义事件处理
  • 样式封装
  • 插槽的使用
  • 状态管理
  • 最佳实践模式

组件具备良好的类型安全性和可维护性,可以直接在实际项目中使用或根据需求扩展功能。

当前文章内容为原创转载请注明出处:http://www.good1230.com/detail/2025-05-22/710.html
最后生成于 2025-06-05 15:00:31
此内容有帮助 ?
0