在 Quasar/Vue 3 企业级应用中,正确的 props 声明方式是:const props = defineProps<{ /* 类型定义 */ }>()
您指出了一个重要的细节。确实,在 Vue 3 的 `<script setup>` 语法中,正确的写法应该是:
```typescript
const props = defineProps<{ userId: string }>()
```
而不是直接使用:
```typescript
defineProps<{ userId: string }>()
```
## 企业级正确写法详解
### 1. 基本用法(推荐)
```typescript
// 定义 props 并赋值给变量
const props = defineProps<{
userId: string
isAdmin?: boolean
role: 'user' | 'admin' | 'manager'
}>()
```
### 2. 带默认值
```typescript
import { withDefaults } from 'vue'
interface UserProps {
userId: string
isAdmin?: boolean
role: 'user' | 'admin' | 'manager'
itemsPerPage?: number
}
const props = withDefaults(defineProps<UserProps>(), {
isAdmin: false,
itemsPerPage: 10
})
```
### 3. 使用接口(企业级推荐)
```typescript
interface ProfileRouteParams {
userId: string
tab?: 'info' | 'security' | 'activity'
viewMode?: 'compact' | 'detailed'
}
const props = defineProps<ProfileRouteParams>()
```
## 为什么需要赋值给变量?
1. **类型推导**:
```typescript
// 正确 - 类型推导可用
const props = defineProps<{ userId: string }>()
console.log(props.userId) // 类型为 string
// 错误 - 无法访问类型化属性
defineProps<{ userId: string }>()
console.log(userId) // 未定义变量
```
2. **模板使用**:
```html
<!-- 模板中可直接使用 props -->
<template>
<div>{{ props.userId }}</div>
</template>
```
3. **响应式处理**:
```typescript
// 可以正确使用 watch
watch(
() => props.userId,
(newId) => {
loadUserData(newId)
}
)
```
## 企业级最佳实践
### 1. 完整类型定义
```typescript
// types/user.d.ts
declare interface UserProfileProps {
userId: string
viewMode?: 'card' | 'table' | 'list'
showSecurityInfo?: boolean
allowEdit?: boolean
initialTab?: 'profile' | 'activity' | 'settings'
}
// 组件中
import type { UserProfileProps } from '@/types/user'
const props = defineProps<UserProfileProps>()
```
### 2. 带验证的复杂参数
```typescript
const props = defineProps<{
userId: string
projectId: string
}>()
// 验证参数格式
const isValidProjectId = computed(() => {
return /^PROJ-\d{4}-\w{3}$/.test(props.projectId)
})
// 使用 watchEffect 响应参数变化
watchEffect(() => {
if (!isValidProjectId.value) {
throw new Error(`Invalid project ID: ${props.projectId}`)
}
loadProjectData(props.projectId)
})
```
### 3. 组合式函数封装
```typescript
// composables/useRouteParams.ts
import { computed } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
export function useValidatedRouteParams(
route: RouteLocationNormalizedLoaded,
schema: Record<string, (val: unknown) => any>
) {
const params: Record<string, any> = {}
for (const [key, validator] of Object.entries(schema)) {
try {
params[key] = validator(route.params[key])
} catch (error) {
console.error(`Invalid route param ${key}:`, error)
throw new Error(`Route validation failed for ${key}`)
}
}
return params
}
// 在组件中使用
import { useRoute } from 'vue-router'
import { useValidatedRouteParams } from '@/composables/useRouteParams'
const route = useRoute()
const params = useValidatedRouteParams(route, {
userId: (val) => {
if (typeof val === 'string' && val.length === 36) return val
throw new Error('Invalid user ID format')
},
projectId: (val) => {
if (typeof val === 'string' && /^PROJ-\d+$/.test(val)) return val
throw new Error('Invalid project ID format')
}
})
```
### 4. 路由配置与组件集成
```typescript
// router/routes.ts
{
path: '/user/:userId/profile/:profileId',
name: 'UserProfile',
component: () => import('@/pages/UserProfile.vue'),
props: route => ({
userId: route.params.userId,
profileId: route.params.profileId,
viewMode: route.query.mode || 'standard'
})
}
// UserProfile.vue
interface ProfileProps {
userId: string
profileId: string
viewMode: 'standard' | 'compact' | 'advanced'
}
const props = defineProps<ProfileProps>()
```
## 多参数传递企业级示例
### 场景:项目报告页面
传递:项目ID、报告类型、时间范围、查看模式
```vue
<script setup lang="ts">
import { withDefaults } from 'vue'
interface ReportParams {
projectId: string
reportType: 'summary' | 'detailed' | 'financial'
dateRange?: [string, string]
viewMode?: 'chart' | 'table' | 'mixed'
maxItems?: number
}
const props = withDefaults(defineProps<ReportParams>(), {
viewMode: 'mixed',
maxItems: 50,
dateRange: () => [new Date().toISOString().split('T')[0], new Date().toISOString().split('T')[0]]
})
// 使用计算属性处理参数
const reportConfig = computed(() => ({
project: props.projectId,
type: props.reportType,
from: props.dateRange[0],
to: props.dateRange[1],
display: props.viewMode,
limit: props.maxItems
}))
// 监听关键参数变化
watch(
() => [props.projectId, props.reportType, props.dateRange],
() => {
loadReportData(reportConfig.value)
},
{ deep: true, immediate: true }
)
</script>
```
## 常见错误处理
### 1. 可选参数处理
```typescript
// 正确:使用可选标记 (?)
const props = defineProps<{
requiredParam: string
optionalParam?: number
}>()
// 访问可选参数
const value = props.optionalParam ?? defaultValue
```
### 2. 复杂类型转换
```typescript
const props = defineProps<{
startDate: string
endDate: string
}>()
// 转换为日期对象
const dateRange = computed(() => [
new Date(props.startDate),
new Date(props.endDate)
])
// 添加验证
const isValidRange = computed(() => {
return dateRange.value[0] < dateRange.value[1]
})
```
### 3. 参数变更事件
```typescript
const emit = defineEmits<{
(e: 'update:viewMode', mode: 'chart' | 'table'): void
(e: 'update:dateRange', range: [string, string]): void
}>()
function changeViewMode(mode: 'chart' | 'table') {
emit('update:viewMode', mode)
}
function updateDateRange(start: string, end: string) {
emit('update:dateRange', [start, end])
}
```
## 总结
在 Quasar/Vue 3 企业级应用中,正确的 props 声明方式是:
```typescript
const props = defineProps<{ /* 类型定义 */ }>()
```
### 关键要点:
1. **必须赋值给变量**(通常命名为 `props`)
2. **使用 TypeScript 接口** 定义复杂类型
3. **可选参数使用 `?` 标记**
4. **默认值使用 `withDefaults`**
5. **企业级应用应添加参数验证**
6. **对关键参数使用 watch 监听变化**
7. **使用组合式函数封装复杂参数逻辑**
遵循这些模式可以创建出类型安全、可维护且健壮的企业级 Vue 组件,能够正确处理简单和复杂的路由参数场景。