eagleye

在 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 组件,能够正确处理简单和复杂的路由参数场景。

posted on 2025-08-06 16:43  GoGrid  阅读(9)  评论(0)    收藏  举报

导航