# Vue3组合式API与TypeScript实战:构建可复用的电商组件库
## 引言
Vue3的组合式API(Composition API)与TypeScript的结合为前端开发者提供了强大的工具来构建可维护、可复用的组件库。电商网站通常包含大量重复使用的UI元素,如商品卡片、价格展示、评分组件等,通过系统性地构建这些组件库可以显著提高开发效率和代码质量。本文将深入探讨如何利用Vue3的组合式API和TypeScript来构建一个可复用的电商组件库,涵盖从基础设置到高级实践的全过程。
## 项目初始化与环境配置
### 创建Vue3项目
首先需要创建一个支持TypeScript的Vue3项目。使用Vue CLI或Vite都可以快速搭建项目基础结构。
“`bash
# 使用Vue CLI
vue create ecommerce-components –preset vue-ts
# 或使用Vite
npm create vite@latest ecommerce-components — –template vue-ts
“`
### 配置TypeScript
确保项目中的TypeScript配置文件(tsconfig.json)正确设置了编译选项,特别是严格模式(strict)和类型检查相关选项。
“`json
{
\”compilerOptions\”: {
\”target\”: \”ES2020\”,
\”useDefineForClassFields\”: true,
\”module\”: \”ESNext\”,
\”lib\”: [\”ES2020\”, \”DOM\”, \”DOM.Iterable\”],
\”skipLibCheck\”: true,
\”moduleResolution\”: \”bundler\”,
\”allowImportingTsExtensions\”: true,
\”resolveJsonModule\”: true,
\”isolatedModules\”: true,
\”noEmit\”: true,
\”jsx\”: \”preserve\”,
\”strict\”: true,
\”noUnusedLocals\”: true,
\”noUnusedParameters\”: true,
\”noFallthroughCasesInSwitch\”: true
},
\”include\”: [\”src/**/*.ts\”, \”src/**/*.d.ts\”, \”src/**/*.tsx\”, \”src/**/*.vue\”],
\”references\”: [{ \”path\”: \”./tsconfig.node.json\” }]
}
“`
### 安装必要依赖
除了Vue3和TypeScript的核心依赖外,还需要安装一些实用的开发工具。
“`bash
npm install vue-tsc @vitejs/plugin-vue -D
“`
## 构建基础电商组件
### 商品卡片组件
商品卡片是电商网站最常见的组件之一。我们将其设计为可复用的组件,支持多种配置选项。
#### 组件结构
“`vue
{{ product.name }}
“`
#### 组合式API实现
“`typescript
import { defineComponent, computed, PropType } from \’vue\’
interface Product {
id: number
name: string
price: number
originalPrice?: number
image: string
rating: number
}
export default defineComponent({
name: \’ProductCard\’,
props: {
product: {
type: Object as PropType,
required: true
},
size: {
type: String as PropType,
default: \’medium\’
},
showOriginalPrice: {
type: Boolean,
default: true
}
},
emits: [\’add-to-cart\’],
setup(props, { emit }) {
const sizeClass = computed(() => `product-card–${props.size}`)
const formattedPrice = computed(() => {
const hasDiscount = props.product.originalPrice && props.product.originalPrice > props.product.price
if (hasDiscount && props.showOriginalPrice) {
return `
¥${props.product.originalPrice.toFixed(2)}
¥${props.product.price.toFixed(2)}
`
}
return `¥${props.product.price.toFixed(2)}`
})
const starRating = computed(() => {
const fullStars = Math.floor(props.product.rating)
const hasHalfStar = props.product.rating % 1 >= 0.5
let stars = \’\’
for (let i = 0; i {
emit(\’add-to-cart\’, props.product)
}
return {
sizeClass,
formattedPrice,
starRating,
addToCart
}
}
})
“`
#### 样式定义
“`css
.product-card {
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
transition: transform 0.3s ease;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.product-card–small {
width: 200px;
}
.product-card–medium {
width: 280px;
}
.product-card–large {
width: 360px;
}
.product-image {
height: 200px;
overflow: hidden;
}
.product-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.product-info {
padding: 15px;
}
.product-name {
margin: 0 0 10px;
font-size: 16px;
color: #333;
}
.product-price {
margin-bottom: 10px;
}
.price-original {
text-decoration: line-through;
color: #999;
margin-right: 8px;
}
.price-current {
color: #e4393c;
font-weight: bold;
}
.product-rating {
margin-bottom: 15px;
color: #666;
}
.stars {
color: #ff9900;
margin-right: 5px;
}
.add-to-cart {
width: 100%;
padding: 8px;
background-color: #e4393c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.add-to-cart:hover {
background-color: #c1272d;
}
“`
### 价格展示组件
电商网站中价格展示需要考虑多种情况,如折扣价、会员价、区间价等。
#### 组件实现
“`vue
{{ formatPrice(originalPrice) }}
{{ formattedPrice }}
{{ unit }}
-{{ discountPercent }}%
import { defineComponent, computed, PropType } from \’vue\’
type Size = \’small\’ | \’medium\’ | \’large\’
export default defineComponent({
name: \’PriceDisplay\’,
props: {
price: {
type: Number,
required: true
},
originalPrice: {
type: Number,
default: undefined
},
size: {
type: String as PropType,
default: \’medium\’
},
showOriginalPrice: {
type: Boolean,
default: true
},
showUnit: {
type: Boolean,
default: true
},
unit: {
type: String,
default: \’¥\’
},
showDiscount: {
type: Boolean,
default: true
}
},
setup(props) {
const sizeClass = computed(() => `price-display–${props.size}`)
const isSale = computed(() => props.originalPrice !== undefined && props.originalPrice > props.price)
const discountPercent = computed(() => {
if (!props.originalPrice || !props.showDiscount) return 0
return Math.round((1 – props.price / props.originalPrice) * 100)
})
const formattedPrice = computed(() => {
return `${props.unit}${props.price.toFixed(2)}`
})
const formatPrice = (value: number) => {
return `${props.unit}${value.toFixed(2)}`
}
return {
sizeClass,
isSale,
discountPercent,
formattedPrice,
formatPrice
}
}
})
“`
### 评分组件
商品评分组件需要支持半星显示和自定义样式。
“`vue
import { defineComponent, computed, PropType } from \’vue\’
type Size = \’small\’ | \’medium\’ | \’large\’
export default defineComponent({
name: \’RatingDisplay\’,
props: {
value: {
type: Number,
required: true,
validator: (val: number) => val >= 0 && val val > 0
},
showValue: {
type: Boolean,
default: true
},
size: {
type: String as PropType,
default: \’medium\’
}
},
setup(props) {
const sizeClass = computed(() => `rating–${props.size}`)
const stars = computed(() => Array.from({ length: props.max }, (_, i) => i + 1))
const fullStars = computed(() => {
const hasHalfStar = props.value % 1 >= 0.5
return Math.floor(props.value) + (hasHalfStar ? 0.5 : 0)
})
const getStarClass = (star: number) => {
if (star <= fullStars.value) return \'full\'
if (star – fullStars.value === 0.5) return \'half\'
return \'empty\'
}
return {
sizeClass,
stars,
fullStars,
getStarClass
}
}
})
“`
## 构建高级组件
### 商品筛选组件
电商网站通常需要复杂的筛选功能,包括分类、品牌、价格区间等。
“`vue
分类
品牌
价格区间
–
import { defineComponent, ref, reactive } from \’vue\’
interface Category {
id: number
name: string
}
interface Brand {
id: number
name: string
}
export default defineComponent({
name: \’ProductFilter\’,
props: {
categories: {
type: Array as PropType,
required: true
},
brands: {
type: Array as PropType,
required: true
},
initialFilters: {
type: Object as PropType,
default: () => ({
categories: [],
brands: [],
priceRange: { min: 0, max: 10000 }
})
}
},
emits: [\’filter-change\’],
setup(props, { emit }) {
const selectedCategories = ref([…props.initialFilters.categories])
const selectedBrands = ref([…props.initialFilters.brands])
const priceRange = reactive({
min: props.initialFilters.priceRange.min,
max: props.initialFilters.priceRange.max
})
const applyFilters = () => {
emit(\’filter-change\’, {
categories: […selectedCategories.value],
brands: […selectedBrands.value],
priceRange: { …priceRange }
})
}
const resetFilters = () => {
selectedCategories.value = []
selectedBrands.value = []
priceRange.min = 0
priceRange.max = 10000
applyFilters()
}
return {
selectedCategories,
selectedBrands,
priceRange,
applyFilters,
resetFilters
}
}
})
“`
### 购物车组件
购物车组件需要管理商品数量、计算总价等功能。
“`vue
购物车
{{ item.name }}
@click=\"decreaseQuantity(item)\"
:disabled=\"item.quantity
–
{{ item.quantity }}
{{ totalItems }}
{{ totalPrice.toFixed(2) }}
import { defineComponent, computed } from \’vue\’
import PriceDisplay from \’./PriceDisplay.vue\’
interface CartItem {
id: number
name: string
price: number
originalPrice?: number
image: string
quantity: number
}
export default defineComponent({
name: \’ShoppingCart\’,
components: {
PriceDisplay
},
props: {
items: {
type: Array as PropType,
required: true
}
},
emits: [\’update:quantity\’, \’remove-item\’],
setup(props, { emit }) {
const totalItems = computed(() => {
return props.items.reduce((sum, item) => sum + item.quantity, 0)
})
const totalPrice = computed(() => {
return props.items.reduce((sum, item) => {
return sum + (item.price * item.quantity)
}, 0)
})
const increaseQuantity = (item: CartItem) => {
emit(\’update:quantity\’, item.id, item.quantity + 1)
}
const decreaseQuantity = (item: CartItem) => {
if (item.quantity > 1) {
emit(\’update:quantity\’, item.id, item.quantity – 1)
}
}
const removeItem = (item: CartItem) => {
emit(\’remove-item\’, item.id)
}
return {
totalItems,
totalPrice,
increaseQuantity,
decreaseQuantity,
removeItem
}
}
})
“`
## 组件库的组织与导出
### 创建统一导出文件

