首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Vue3 结合 TypeScript 项目开发使用指南及组件封装实操方法

Vue3 结合 TypeScript 项目开发使用指南及组件封装实操方法

原创
作者头像
小焱
发布于 2025-05-29 00:12:16
发布于 2025-05-29 00:12:16
25000
代码可运行
举报
文章被收录于专栏:前端开发前端开发
运行总次数:0
代码可运行

Vue3+TypeScript项目使用指南与组件封装方法

一、项目初始化与基础使用

(一)项目创建流程

代码语言:bash
AI代码解释
复制
# 创建Vue3+TypeScript项目
npm init vite@latest my-vue3-ts-app -- --template vue-ts

# 进入项目目录
cd my-vue3-ts-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

(二)项目常用命令

命令

描述

npm run dev

启动开发服务器

npm run build

构建生产环境版本

npm run preview

预览生产环境构建结果

npm run lint

代码检查

npm run format

代码格式化

二、组件封装最佳实践

(一)基础Button组件封装

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- components/BaseButton.vue -->
<template>
  <button
    :class="[
      'px-4 py-2 rounded transition-all duration-200',
      variantClass,
      sizeClass,
      { 'opacity-75 cursor-not-allowed': disabled },
    ]"
    :disabled="disabled"
    @click="handleClick"
  >
    <slot />
  </button>
</template>

<script lang="ts" setup>
import { computed, defineProps, defineEmits } from 'vue';

const props = defineProps({
  variant: {
    type: String,
    default: 'primary',
    validator: (value: string) => ['primary', 'secondary', 'success', 'danger', 'warning', 'info'].includes(value),
  },
  size: {
    type: String,
    default: 'medium',
    validator: (value: string) => ['small', 'medium', 'large'].includes(value),
  },
  disabled: {
    type: Boolean,
    default: false,
  },
});

const emits = defineEmits(['click']);

const variantClass = computed(() => {
  const classes = {
    primary: 'bg-primary text-white hover:bg-primary/90',
    secondary: 'bg-secondary text-white hover:bg-secondary/90',
    success: 'bg-success text-white hover:bg-success/90',
    danger: 'bg-danger text-white hover:bg-danger/90',
    warning: 'bg-warning text-white hover:bg-warning/90',
    info: 'bg-info text-white hover:bg-info/90',
  };
  return classes[props.variant];
});

const sizeClass = computed(() => {
  const classes = {
    small: 'text-sm',
    medium: 'text-base',
    large: 'text-lg',
  };
  return classes[props.size];
});

const handleClick = (event: MouseEvent) => {
  if (!props.disabled) {
    emits('click', event);
  }
};
</script>

(二)Input组件封装

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- components/BaseInput.vue -->
<template>
  <div class="form-group">
    <label v-if="label" :for="id" class="block text-sm font-medium text-gray-700 mb-1">
      {{ label }}
    </label>
    <div class="relative">
      <input
        :id="id"
        :type="type"
        :value="modelValue"
        :placeholder="placeholder"
        :disabled="disabled"
        :readonly="readonly"
        :class="[
          'w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50',
          {
            'border-gray-300': !error,
            'border-red-300 focus:ring-red-500': error,
            'bg-gray-100 cursor-not-allowed': disabled || readonly,
          },
        ]"
        @input="$emit('update:modelValue', $event.target.value)"
        @blur="$emit('blur', $event)"
        @focus="$emit('focus', $event)"
      />
      <span v-if="prefix" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">
        {{ prefix }}
      </span>
      <span v-if="suffix" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500">
        {{ suffix }}
      </span>
    </div>
    <p v-if="error" class="mt-1 text-sm text-red-500">{{ error }}</p>
  </div>
</template>

<script lang="ts" setup>
import { computed, defineProps, defineEmits } from 'vue';

const props = defineProps({
  modelValue: {
    type: [String, Number],
    default: '',
  },
  label: {
    type: String,
    default: '',
  },
  type: {
    type: String,
    default: 'text',
  },
  placeholder: {
    type: String,
    default: '',
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  readonly: {
    type: Boolean,
    default: false,
  },
  error: {
    type: String,
    default: '',
  },
  prefix: {
    type: String,
    default: '',
  },
  suffix: {
    type: String,
    default: '',
  },
  id: {
    type: String,
    default: () => `input-${Math.random().toString(36).substring(2, 10)}`,
  },
});

const emits = defineEmits(['update:modelValue', 'blur', 'focus']);
</script>

三、组合式函数开发

(一)表单验证组合式函数

代码语言:typescript
AI代码解释
复制
// composables/useFormValidation.ts
import { reactive, computed, ref } from 'vue';

export const useFormValidation = <T extends Record<string, any>>(
  initialValues: T,
  validationRules: {
    [K in keyof T]?: {
      required?: boolean;
      pattern?: RegExp;
      minLength?: number;
      maxLength?: number;
      custom?: (value: T[K]) => boolean;
      message?: string;
    }[];
  }
) => {
  const formData = reactive({ ...initialValues });
  const errors = reactive<{ [K in keyof T]?: string }>({});
  
  const validateField = (field: keyof T) => {
    if (!validationRules[field]) return true;
    
    const value = formData[field];
    const rules = validationRules[field]!;
    let isValid = true;
    
    errors[field] = '';
    
    for (const rule of rules) {
      if (rule.required && !value) {
        errors[field] = rule.message || '此字段为必填项';
        isValid = false;
        break;
      }
      
      if (rule.pattern && value && !rule.pattern.test(String(value))) {
        errors[field] = rule.message || '格式不正确';
        isValid = false;
        break;
      }
      
      if (rule.minLength && value && String(value).length < rule.minLength) {
        errors[field] = rule.message || `最少需要${rule.minLength}个字符`;
        isValid = false;
        break;
      }
      
      if (rule.maxLength && value && String(value).length > rule.maxLength) {
        errors[field] = rule.message || `最多不能超过${rule.maxLength}个字符`;
        isValid = false;
        break;
      }
      
      if (rule.custom && !rule.custom(value)) {
        errors[field] = rule.message || '验证失败';
        isValid = false;
        break;
      }
    }
    
    return isValid;
  };
  
  const validateAll = () => {
    let isValid = true;
    
    for (const field in validationRules) {
      if (!validateField(field as keyof T)) {
        isValid = false;
      }
    }
    
    return isValid;
  };
  
  const resetForm = () => {
    for (const field in formData) {
      formData[field as keyof T] = initialValues[field as keyof T];
      errors[field as keyof T] = '';
    }
  };
  
  const setFieldValue = (field: keyof T, value: T[field]) => {
    formData[field] = value;
    validateField(field);
  };
  
  const isFormValid = computed(() => {
    for (const field in errors) {
      if (errors[field as keyof T]) {
        return false;
      }
    }
    return true;
  });
  
  return {
    formData,
    errors,
    validateField,
    validateAll,
    resetForm,
    setFieldValue,
    isFormValid,
  };
};

(二)API请求组合式函数

代码语言:typescript
AI代码解释
复制
// composables/useApi.ts
import { ref, computed, onMounted, watch } from 'vue';
import axios, { AxiosRequestConfig } from 'axios';

export const useApi = <T = any>(url: string, options: {
  method?: 'get' | 'post' | 'put' | 'delete' | 'patch';
  params?: Record<string, any>;
  data?: Record<string, any>;
  headers?: Record<string, any>;
  autoFetch?: boolean;
  watchParams?: any;
  transformData?: (data: any) => T;
  errorHandler?: (error: any) => string;
  config?: AxiosRequestConfig;
} = {}) => {
  const data = ref<T | null>(null);
  const loading = ref(false);
  const error = ref<string | null>(null);
  const response = ref<any | null>(null);
  
  const fetchData = async (params: Record<string, any> = {}) => {
    loading.value = true;
    error.value = null;
    
    try {
      const config: AxiosRequestConfig = {
        method: options.method || 'get',
        url,
        params: options.method === 'get' ? { ...options.params, ...params } : options.params,
        data: options.method !== 'get' ? { ...options.data, ...params } : options.data,
        headers: options.headers || {},
        ...options.config,
      };
      
      const res = await axios(config);
      response.value = res;
      data.value = options.transformData ? options.transformData(res.data) : res.data;
    } catch (err) {
      error.value = options.errorHandler ? options.errorHandler(err) : (err as Error).message;
    } finally {
      loading.value = false;
    }
  };
  
  if (options.autoFetch !== false) {
    onMounted(fetchData);
  }
  
  if (options.watchParams) {
    watch(options.watchParams, fetchData, { deep: true });
  }
  
  return {
    data,
    loading,
    error,
    response,
    fetchData,
  };
};

四、状态管理与路由

(一)Pinia状态管理

代码语言:typescript
AI代码解释
复制
// stores/counter.ts
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    title: 'Pinia Counter',
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
    countPlusOne: (state) => state.count + 1,
  },
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
    incrementBy(value: number) {
      this.count += value;
    },
    reset() {
      this.count = 0;
    },
  },
});

(二)Vue Router配置

代码语言:typescript
AI代码解释
复制
// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import AboutView from '../views/AboutView.vue';
import ProductView from '../views/ProductView.vue';
import NotFoundView from '../views/NotFoundView.vue';

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: HomeView,
    meta: {
      title: '首页',
      requiresAuth: false,
    },
  },
  {
    path: '/about',
    name: 'About',
    component: AboutView,
    meta: {
      title: '关于我们',
      requiresAuth: false,
    },
  },
  {
    path: '/product/:id',
    name: 'Product',
    component: ProductView,
    meta: {
      title: '商品详情',
      requiresAuth: false,
    },
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: NotFoundView,
    meta: {
      title: '页面不存在',
      requiresAuth: false,
    },
  },
];

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    } else {
      return { top: 0 };
    }
  },
});

// 路由守卫
router.beforeEach((to, from, next) => {
  document.title = to.meta.title || 'Vue App';
  
  // 身份验证示例
  const isAuthenticated = localStorage.getItem('token');
  if (to.meta.requiresAuth && !isAuthenticated) {
    next({ name: 'Login' });
  } else {
    next();
  }
});

export default router;

五、使用示例

(一)在组件中使用基础组件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- views/LoginView.vue -->
<template>
  <div class="container mx-auto px-4 py-12 max-w-md">
    <div class="bg-white rounded-xl shadow-lg p-8">
      <h2 class="text-2xl font-bold text-center mb-6">登录</h2>
      
      <form @submit.prevent="handleLogin">
        <BaseInput
          v-model="formData.username"
          label="用户名"
          placeholder="请输入用户名"
          :error="errors.username"
          @blur="validateField('username')"
        />
        
        <BaseInput
          v-model="formData.password"
          type="password"
          label="密码"
          placeholder="请输入密码"
          :error="errors.password"
          @blur="validateField('password')"
          class="mt-4"
        />
        
        <div class="mt-6">
          <BaseButton variant="primary" size="large" block>
            登录
          </BaseButton>
        </div>
      </form>
      
      <div class="mt-6 text-center">
        <router-link to="/register" class="text-primary hover:underline">
          注册账号
        </router-link>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { useFormValidation } from '@/composables/useFormValidation';
import BaseInput from '@/components/BaseInput.vue';
import BaseButton from '@/components/BaseButton.vue';

const { formData, errors, validateField, validateAll } = useFormValidation(
  {
    username: '',
    password: '',
  },
  {
    username: [
      { required: true, message: '请输入用户名' },
      { minLength: 3, message: '用户名至少3个字符' },
    ],
    password: [
      { required: true, message: '请输入密码' },
      { minLength: 6, message: '密码至少6个字符' },
    ],
  }
);

const handleLogin = () => {
  if (validateAll()) {
    // 登录逻辑
    console.log('登录成功:', formData);
  }
};
</script>

(二)使用组合式函数请求API

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- views/ProductsView.vue -->
<template>
  <div class="container mx-auto px-4 py-8">
    <h1 class="text-3xl font-bold mb-6">商品列表</h1>
    
    <div v-if="loading" class="text-center py-12">
      <i class="fa fa-spinner fa-spin text-2xl text-primary"></i>
      <p class="mt-2">加载中...</p>
    </div>
    
    <div v-else-if="error" class="text-center py-12 text-red-500">
      <i class="fa fa-exclamation-circle text-2xl"></i>
      <p class="mt-2">{{ error }}</p>
    </div>
    
    <div v-else class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
      <div v-for="product in products" :key="product.id" class="bg-white rounded-lg shadow-md overflow-hidden transition-all duration-300 hover:shadow-lg">
        <img :src="product.image" alt="product.name" class="w-full h-48 object-cover">
        <div class="p-4">
          <h3 class="font-bold text-lg mb-2">{{ product.name }}</h3>
          <p class="text-gray-600 mb-4">{{ product.description }}</p>
          <div class="flex justify-between items-center">
            <span class="font-bold text-primary text-xl">{{ product.price }}</span>
            <BaseButton variant="primary" size="small">
              加入购物车
            </BaseButton>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { useApi } from '@/composables/useApi';
import BaseButton from '@/components/BaseButton.vue';

interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  image: string;
}

const { data: products, loading, error, fetchData } = useApi<Product[]>('/api/products', {
  method: 'get',
  autoFetch: true,
});
</script>

六、总结

通过以上组件封装和组合式函数开发方法,你可以:

  1. 提高代码复用性:通过基础组件和组合式函数减少重复代码
  2. 简化开发流程:通过封装常用功能,降低开发复杂度
  3. 提升项目可维护性:统一的组件风格和逻辑结构使代码更易维护
  4. 加速开发速度:使用已封装的组件和函数快速构建页面

这些方法与Vue3+TypeScript项目结合,可以帮助你高效构建高质量的前端应用。


Vue3,TypeScript, 项目开发,指南,组件封装,实操方法,前端开发,JavaScript, 响应式编程,组合式 API, 单文件组件,TypeScript 类型,组件通信,状态管理,工程化



资源地址:

https://pan.quark.cn/s/85ab8f4cc743


原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Vue3+TypeScript项目使用指南与组件封装方法
    • 一、项目初始化与基础使用
      • (一)项目创建流程
      • (二)项目常用命令
    • 二、组件封装最佳实践
      • (一)基础Button组件封装
      • (二)Input组件封装
    • 三、组合式函数开发
      • (一)表单验证组合式函数
      • (二)API请求组合式函数
    • 四、状态管理与路由
      • (一)Pinia状态管理
      • (二)Vue Router配置
    • 五、使用示例
      • (一)在组件中使用基础组件
      • (二)使用组合式函数请求API
    • 六、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档