eagleye

Quasar ref 企业级实用教程

# Quasar ref 企业级实用教程

## 1. 理解 Vue ref 与 Quasar 的结合

在企业级 Quasar 应用开发中,`ref` 系统是 Vue 3 组合式 API 的核心特性,用于创建响应式引用和访问 DOM 元素或组件实例。

### 1.1 基本概念

```typescript
// 创建响应式数据引用
const count = ref(0)

// 创建模板引用(DOM 元素)
const myElement = ref<HTMLElement | null>(null)

// 创建组件引用
const myComponent = ref<ComponentPublicInstance | null>(null)
```

## 2. Quasar 组件 ref 的企业级用法

### 2.1 引用 Quasar 组件

```vue
<template>
<!-- 引用 Quasar 组件 -->
<q-dialog ref="dialogRef" persistent>
<!-- 对话框内容 -->
</q-dialog>

<q-form ref="formRef" @submit="onSubmit">
<!-- 表单内容 -->
</q-form>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import type { QDialog, QForm } from 'quasar'

// 正确类型化的组件引用
const dialogRef = ref<InstanceType<typeof QDialog> | null>(null)
const formRef = ref<InstanceType<typeof QForm> | null>(null)

// 使用组件方法
const openDialog = () => {
dialogRef.value?.show()
}

const validateForm = () => {
formRef.value?.validate().then(valid => {
if (valid) {
// 表单验证通过
}
})
}
</script>
```

### 2.2 企业级最佳实践

```typescript
// 创建可复用的 ref 类型定义
type QuasarRef<T extends abstract new (...args: any) => any> =
Ref<InstanceType<T> | null>

// 使用类型工具
const createQuasarRef = <T extends abstract new (...args: any) => any>() => {
return ref<InstanceType<T> | null>(null)
}

// 在企业级应用中使用
const dialogRef = createQuasarRef<typeof QDialog>()
const formRef = createQuasarRef<typeof QForm>()
const tableRef = createQuasarRef<typeof QTable>()
```

## 3. 企业级表单处理与 ref

### 3.1 复杂表单管理

```vue
<template>
<q-form ref="formRef" class="q-gutter-md">
<q-input
ref="emailInputRef"
v-model="formData.email"
label="邮箱"
:rules="emailRules"
/>

<q-input
ref="passwordInputRef"
v-model="formData.password"
label="密码"
:type="isPwdVisible ? 'text' : 'password'"
:rules="passwordRules"
/>

<q-btn label="提交" @click="validateForm" />
</q-form>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { QForm, QInput } from 'quasar'

// 表单引用
const formRef = ref<InstanceType<typeof QForm> | null>(null)
const emailInputRef = ref<InstanceType<typeof QInput> | null>(null)
const passwordInputRef = ref<InstanceType<typeof QInput> | null>(null)

// 表单数据
const formData = reactive({
email: '',
password: ''
})

// 验证规则
const emailRules = [
(val: string) => !!val || '邮箱不能为空',
(val: string) => /.+@.+\..+/.test(val) || '请输入有效的邮箱地址'
]

const passwordRules = [
(val: string) => !!val || '密码不能为空',
(val: string) => val.length >= 8 || '密码至少8位'
]

// 企业级表单验证
const validateForm = async () => {
if (!formRef.value) return

try {
const isValid = await formRef.value.validate()
if (isValid) {
// 提交表单
await submitForm()
} else {
// 聚焦到第一个错误字段
focusFirstInvalidField()
}
} catch (error) {
console.error('表单验证错误:', error)
}
}

// 聚焦到第一个无效字段
const focusFirstInvalidField = () => {
if (emailInputRef.value?.hasError) {
emailInputRef.value.focus()
} else if (passwordInputRef.value?.hasError) {
passwordInputRef.value.focus()
}
}

// 提交表单
const submitForm = async () => {
// 企业级API调用
try {
// ... 提交逻辑
} catch (error) {
// 错误处理
}
}
</script>
```

## 4. Quasar 组件方法与 ref 的深度集成

### 4.1 QTable 的高级用法

```vue
<template>
<q-table
ref="tableRef"
:rows="rows"
:columns="columns"
row-key="id"
selection="multiple"
v-model:selected="selected"
>
<!-- 表格内容 -->
</q-table>

<div class="q-mt-md">
<q-btn label="全选" @click="selectAll" />
<q-btn label="清除选择" @click="clearSelection" />
<q-btn label="刷新数据" @click="refreshTable" />
</div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { QTable } from 'quasar'

// 表格引用
const tableRef = ref<InstanceType<typeof QTable> | null>(null)

// 表格数据
const rows = ref([])
const selected = ref([])
const columns = [
{ name: 'id', label: 'ID', field: 'id', sortable: true },
{ name: 'name', label: '名称', field: 'name', sortable: true },
// 更多列...
]

// 组件挂载后访问表格方法
onMounted(() => {
// 可以在这里初始化表格状态
})

// 企业级表格操作方法
const selectAll = () => {
if (tableRef.value) {
tableRef.value.selectAllRows()
}
}

const clearSelection = () => {
if (tableRef.value) {
tableRef.value.clearSelection()
}
}

const refreshTable = async () => {
if (tableRef.value) {
// 显示加载状态
tableRef.value.loading = true

try {
// 获取新数据
const newData = await fetchTableData()
rows.value = newData

// 重置排序和筛选(如果有)
tableRef.value.resetSortAndFilter()
} catch (error) {
console.error('刷新表格数据失败:', error)
} finally {
tableRef.value.loading = false
}
}
}

// 模拟获取数据
const fetchTableData = async () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 1, name: '项目一' },
{ id: 2, name: '项目二' },
// 更多数据...
])
}, 1000)
})
}
</script>
```

### 4.2 QDialog 的高级控制

```vue
<template>
<q-dialog ref="dialogRef" persistent>
<q-card style="min-width: 350px">
<q-card-section>
<div class="text-h6">企业级对话框</div>
</q-card-section>

<q-card-section class="q-pt-none">
<!-- 对话框内容 -->
</q-card-section>

<q-card-actions align="right" class="text-primary">
<q-btn flat label="取消" @click="closeDialog" />
<q-btn flat label确认" @click="confirmDialog" />
</q-card-actions>
</q-card>
</q-dialog>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import type { QDialog } from 'quasar'

// 对话框引用
const dialogRef = ref<InstanceType<typeof QDialog> | null>(null)

// 企业级对话框控制方法
const openDialog = () => {
if (dialogRef.value) {
dialogRef.value.show()
}
}

const closeDialog = () => {
if (dialogRef.value) {
dialogRef.value.hide()
}
}

const confirmDialog = async () => {
if (dialogRef.value) {
// 执行确认操作
await performConfirmAction()

// 关闭对话框
dialogRef.value.hide()
}
}

const performConfirmAction = async () => {
// 企业级业务逻辑
}

// 暴露方法给父组件
defineExpose({
openDialog,
closeDialog
})
</script>
```

## 5. 企业级自定义组件与 ref

### 5.1 创建可复用的自定义组件

```vue
<!-- EnterpriseInput.vue -->
<template>
<q-input
ref="inputRef"
v-bind="$attrs"
:model-value="modelValue"
@update:model-value="$emit('update:modelValue', $event)"
>
<template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</q-input>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import type { QInput } from 'quasar'

const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
}
})

defineEmits(['update:modelValue'])

// 内部输入框引用
const inputRef = ref<InstanceType<typeof QInput> | null>(null)

// 暴露方法给父组件
defineExpose({
focus: () => inputRef.value?.focus(),
blur: () => inputRef.value?.blur(),
select: () => inputRef.value?.select(),
hasError: () => inputRef.value?.hasError
})

// 企业级功能:自动格式化
watch(() => props.modelValue, (newValue) => {
// 可以在这里添加格式化逻辑
}, { immediate: true })
</script>
```

### 5.2 使用自定义组件

```vue
<template>
<EnterpriseInput
ref="enterpriseInputRef"
v-model="userData.email"
label="企业邮箱"
:rules="emailRules"
/>

<q-btn label="聚焦输入框" @click="focusInput" />
</template>

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

// 自定义组件引用
const enterpriseInputRef = ref<InstanceType<typeof EnterpriseInput> | null>(null)

const userData = reactive({
email: ''
})

const emailRules = [
(val: string) => !!val || '邮箱不能为空',
(val: string) => /.+@.+\..+/.test(val) || '请输入有效的邮箱地址'
]

// 使用自定义组件暴露的方法
const focusInput = () => {
enterpriseInputRef.value?.focus()
}
</script>
```

## 6. 企业级 ref 模式与最佳实践

### 6.1 创建 ref 工具函数

```typescript
// src/utils/refs.ts

/**
* 创建类型安全的 Quasar 组件引用
*/
export const createComponentRef = <T extends abstract new (...args: any) => any>() => {
return ref<InstanceType<T> | null>(null)
}

/**
* 创建带有初始值的组件引用
*/
export const createInitializedComponentRef = <T extends abstract new (...args: any) => any>(
initializer: (instance: InstanceType<T>) => void
) => {
const ref = createComponentRef<T>()

watch(ref, (newInstance) => {
if (newInstance) {
initializer(newInstance)
}
}, { immediate: true })

return ref
}

/**
* 批量创建组件引用
*/
export const createComponentRefs = <
T extends abstract new (...args: any) => any,
K extends string
>(components: Record<K, T>) => {
const refs = {} as Record<K, Ref<InstanceType<T> | null>>

Object.keys(components).forEach(key => {
refs[key as K] = createComponentRef<T>()
})

return refs
}
```

### 6.2 在企业级应用中使用

```typescript
// 使用工具函数创建引用
import { createComponentRef, createComponentRefs } from 'src/utils/refs'
import type { QDialog, QForm, QTable } from 'quasar'

// 单个引用
const dialogRef = createComponentRef<typeof QDialog>()

// 多个引用
const { formRef, tableRef } = createComponentRefs({
formRef: QForm,
tableRef: QTable
})

// 带初始化的引用
const initializedDialogRef = createInitializedComponentRef<typeof QDialog>((dialog) => {
// 对话框初始化逻辑
dialog.onOk(() => console.log('对话框确认'))
dialog.onHide(() => console.log('对话框隐藏'))
})
```

## 7. 测试与调试

### 7.1 单元测试中的 ref 处理

```typescript
// EnterpriseInput.spec.ts
import { mount } from '@vue/test-utils'
import EnterpriseInput from './EnterpriseInput.vue'
import { QInput } from 'quasar'

describe('EnterpriseInput', () => {
it('应该暴露焦点方法', async () => {
const wrapper = mount(EnterpriseInput, {
props: {
modelValue: ''
}
})

// 获取组件实例
const inputInstance = wrapper.findComponent(QInput).vm

// 模拟焦点方法
const focusSpy = vi.spyOn(inputInstance, 'focus')

// 获取自定义组件实例
const enterpriseInput = wrapper.vm

// 调用暴露的方法
enterpriseInput.focus()

// 断言焦点方法被调用
expect(focusSpy).toHaveBeenCalled()
})
})
```

### 7.2 调试技巧

```typescript
// 在开发环境中添加调试功能
if (process.env.DEV) {
watch(dialogRef, (newInstance) => {
if (newInstance) {
// 添加调试信息
console.log('对话框实例已创建:', newInstance)

// 在开发工具中可访问
window.$dialog = newInstance
}
}, { immediate: true })
}
```

## 8. 性能优化与注意事项

### 8.1 避免不必要的 ref 使用

```typescript
// 不好的做法:过度使用 ref
const unnecessaryRef = ref(null)

// 好的做法:只在需要时使用 ref
// 1. 需要调用组件方法时
// 2. 需要访问 DOM 元素时
// 3. 需要与第三方库集成时
```

### 8.2 内存管理

```typescript
import { onUnmounted } from 'vue'

// 清理 ref 相关的资源
onUnmounted(() => {
// 清理事件监听器
// 取消定时器
// 释放大型对象
})
```

## 总结

在企业级 Quasar 应用中使用 `ref` 时,应遵循以下原则:

1. **类型安全**:始终为 ref 提供正确的 TypeScript 类型
2. **封装性**:通过 `defineExpose` 有选择地暴露组件方法
3. **可复用性**:创建可复用的 ref 工具函数和模式
4. **测试友好**:设计易于测试的组件接口
5. **性能意识**:避免不必要的 ref 使用,及时清理资源

通过遵循这些企业级最佳实践,您可以创建出更健壮、可维护和可扩展的 Quasar 应用程序。

posted on 2025-08-24 18:15  GoGrid  阅读(19)  评论(0)    收藏  举报

导航