Vue 基础(实战模板与命名指南)
聚焦三件事:模板化写法、ref/reactive
使用选择、变量与函数命名规范。配合可复制的代码模板,快速搭建可靠页面。
1. 组件标准模板
vue
<template>
<section class="user-list-page">
<header class="page-header">
<h2>用户列表</h2>
<div class="actions">
<el-input v-model="query.keyword" placeholder="关键字" class="mr-8" />
<el-button type="primary" :loading="isLoading" @click="fetchUsers">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
</header>
<el-table :data="rows" v-loading="isLoading">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="姓名" />
<el-table-column prop="phone" label="手机" />
<el-table-column label="操作" width="160" align="center">
<template #default="{ row }">
<el-button size="small" @click="onEdit(row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="mt-12"
background
layout="prev, pager, next, sizes, total"
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
@update:current-page="onPageChange"
@update:page-size="onPageSizeChange"
/>
</section>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
// 查询条件:对象推荐用 reactive(可读性强,字段集中)
const query = reactive({ keyword: '', dateRange: [] as [string, string] | [] })
// 列表数据:数组通常用 ref,便于整体替换(分页、筛选场景)
const rows = ref<any[]>([])
const isLoading = ref(false)
// 分页:对象一次性传递,使用 ref 包裹以便整体更新
const pagination = ref({ page: 1, pageSize: 10, total: 0 })
// 计算属性:派生状态(示例)
const hasKeyword = computed(() => query.keyword.trim().length > 0)
// 事件处理:动作动词开头 + 语义对象(handle/on 均可,保持一致)
function fetchUsers() {
isLoading.value = true
try {
const total = 42
const start = (pagination.value.page - 1) * pagination.value.pageSize
const end = start + pagination.value.pageSize
const all = Array.from({ length: total }).map((_, i) => ({ id: i + 1, name: `用户${i + 1}`, phone: '13800000000' }))
rows.value = hasKeyword.value ? all.filter(u => u.name.includes(query.keyword)) .slice(start, end) : all.slice(start, end)
pagination.value.total = hasKeyword.value ? all.filter(u => u.name.includes(query.keyword)).length : total
} finally {
isLoading.value = false
}
}
function resetQuery() {
query.keyword = ''
query.dateRange = []
pagination.value.page = 1
fetchUsers()
}
function onPageChange(p: number) {
pagination.value.page = p
fetchUsers()
}
function onPageSizeChange(s: number) {
pagination.value.pageSize = s
pagination.value.page = 1
fetchUsers()
}
function onEdit(row: any) {
console.log('edit', row)
}
// 初始化
fetchUsers()
</script>
<style scoped>
.page-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.actions { display: flex; align-items: center; }
.mr-8 { margin-right: 8px; }
.mt-12 { margin-top: 12px; }
</style>
2. ref
vs reactive
使用选择
- 基础类型(数值、字符串、布尔)用
ref
- 示例:
const count = ref(0)
、const isLoading = ref(false)
- 示例:
- 复杂对象/表单状态用
reactive
- 示例:
const form = reactive({ name: '', age: 0 })
- 示例:
- 数组在分页/筛选场景常用
ref<any[]>([])
,便于整体替换- 赋值:
rows.value = newRows
- 变更:
rows.value.push(item)
(注意.value
)
- 赋值:
- 嵌套对象:
reactive
自动深层代理;若需要整体替换用ref<{...}>()
常见陷阱:
- 模板中
ref
会自动解包;脚本中需.value
- 对
reactive
使用解构会失去响应式,改用toRefs
或在模板直接访问字段
3. 命名规范(变量、函数、计算属性、事件)
- 状态布尔:
isLoading
、isVisible
、hasError
、isDisabled
- 数据集合:
users
、rows
、items
;单项用user
、row
、item
- 查询与表单:
query
、filters
、form
- 计算属性:用派生语义,如
filteredUsers
、selectedCount
- 事件处理:统一前缀
on
或handle
,如onSubmit
、handleDelete
- 接口函数:动作 + 资源,如
fetchUsers
、createUser
、updateUser
- 组件 props:语义清晰,避免缩写;双向绑定使用
modelValue
4. 模板语法常用模式
vue
<div>
<!-- 插值 -->
<div>当前计数:{{ count }}</div>
<!-- 属性绑定 -->
<img :src="imgUrl" :alt="altText" />
<button :disabled="isDisabled">保存</button>
<!-- class/style 绑定 -->
<div :class="{ active: isActive }" :style="{ color: textColor }">样式示例</div>
<!-- 事件绑定 -->
<button @click="onClick">点击</button>
<!-- 条件/列表 -->
<p v-if="isShow">显示内容</p>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
5. 计算属性与侦听器模板
ts
import { computed, watch } from 'vue'
// 计算属性:依赖数据变化自动更新
const isAllChecked = computed(() => items.value.every(i => i.checked))
// 侦听:响应变化执行副作用
watch(() => query.keyword, (newVal, oldVal) => {
// 节流/防抖可在此接入
fetchUsers()
})
// 深度与立即选项
watch(form, (val) => { console.log('form changed', val) }, { deep: true })
watch(() => route.path, () => init(), { immediate: true })
6. 组件通信模板(props / emits / v-model)
vue
<!-- 子组件 Child.vue -->
<script setup lang="ts">
import type { PropType } from 'vue'
const props = defineProps({
modelValue: { type: String, default: '' },
maxLength: { type: Number, default: 20 }
})
const emit = defineEmits<{
(e: 'update:modelValue', v: string): void
(e: 'change', v: string): void
}>()
function onInput(v: string) {
emit('update:modelValue', v)
emit('change', v)
}
</script>
<template>
<el-input :model-value="props.modelValue" :maxlength="props.maxLength" @input="onInput" />
</template>
<!-- 父组件使用 -->
<Child v-model="name" @change="handleNameChange" />
补充:
- 子组件事件示例:
const emit = defineEmits(['change']); emit('change', newVal)
- 父组件监听:
<Child @change="handleChildChange" />
7. 可复用逻辑(Composable)模板
分页:
ts
import { ref } from 'vue'
export function usePagination(initial = { page: 1, pageSize: 10, total: 0 }) {
const pagination = ref(initial)
const onPageChange = (p: number) => (pagination.value.page = p)
const onPageSizeChange = (s: number) => { pagination.value.pageSize = s; pagination.value.page = 1 }
return { pagination, onPageChange, onPageSizeChange }
}
数据获取:
ts
import { ref } from 'vue'
import http from '@/api/http'
export function useFetchUsers() {
const rows = ref<any[]>([])
const isLoading = ref(false)
async function fetch(params: { page: number; pageSize: number; keyword?: string }) {
isLoading.value = true
try {
const res = await http.get('/users', { params })
rows.value = res.items
} finally {
isLoading.value = false
}
}
return { rows, isLoading, fetch }
}
8. 表单状态与校验(简单版)
vue
<script setup lang="ts">
import { reactive, ref } from 'vue'
const formRef = ref()
const form = reactive({ name: '', age: 18 })
const rules = {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
age: [{ type: 'number', required: true, message: '请输入年龄', trigger: 'blur' }]
}
async function onSubmit() {
await formRef.value.validate()
// 调用接口提交
}
</script>
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input-number v-model="form.age" :min="0" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
</template>
9. 小结与实践建议
- 页面数据结构优先:
query
(reactive)+rows
(ref)+pagination
(ref) - 动作统一:
fetchXxx
、resetXxx
、onXxxChange
保持一致命名 - 组件通信明确:
props
提数据,emits
发事件,双向绑定用v-model
- 抽象通用:将分页/获取/表单校验封装为 composable 或通用组件