Vue 3 + TypeScript常用核心概念的系统性总结与最佳实践,适合放在手边的实用工具文档。
一、响应式数据:ref 与 reactive
1. ref
用于定义基本类型或需要响应式解包的对象引用。
import { ref } from 'vue'
const count = ref<number>(0)
const name = ref<string>('Vue')
// 修改
count.value++
// 在模板中自动解包:{{ count }}
⚠️ 注意:在 JS/TS 中必须用
.value,模板中自动解包。
2. reactive
用于定义响应式对象(不推荐用于基本类型)。
import { reactive } from 'vue'
interface User {
name: string
age: number
}
const user = reactive<User>({
name: 'Alice',
age: 20
})
user.age++ // 直接操作,无需 .value
❗ 注意:
reactive不会为基本类型创建响应式,且解构会失去响应性。
3. ref vs reactive 使用建议
| 场景 | 推荐 |
|---|---|
| 基本类型(number, string, boolean) | ref |
| 对象或复杂结构 | reactive 或 ref<object> |
| 需要解构赋值 | ref + .value 更安全 |
想在 setup 返回多个变量 | ref 更方便 |
二、props:父子组件传值(TS 类型安全)
<!-- Child.vue -->
<script setup lang="ts">
interface Props {
title: string
disabled?: boolean
count?: number
}
// 定义默认值 + 类型
const props = withDefaults(defineProps<Props>(), {
disabled: false,
count: 0
})
</script>
<template>
<div :class="{ disabled }">{{ title }} ({{ count }})</div>
</template>
<!-- Parent.vue -->
<template>
<Child title="Hello" :count="5" />
</template>
要点:
- 使用
defineProps+withDefaults实现类型安全和默认值- 避免使用
any,始终定义接口
三、emit:子组件向父组件触发事件
<!-- Child.vue -->
<script setup lang="ts">
// 定义触发的事件
const emit = defineEmits<{
(e: 'update', id: number): void
(e: 'close'): void
}>()
// 使用
function handleClick() {
emit('update', 123)
}
</script>
<!-- Parent.vue -->
<Child @update="onUpdate" @close="onClose" />
类型安全:使用对象语法定义事件签名,避免字符串拼写错误。
四、computed:计算属性
import { computed, ref } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 可写 computed(少见)
const writableName = computed({
get: () => fullName.value,
set: (val) => {
[firstName.value, lastName.value] = val.split(' ')
}
})
特点:缓存、依赖自动追踪、性能优化。
五、watch 与 watchEffect:侦听变化
1. watch:侦听特定数据源
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log(`count changed: ${oldVal} -> ${newVal}`)
})
// 侦听多个源
watch([count, name], ([c, n], [oldC, oldN]) => {
// ...
})
// 深层侦听对象
watch(
() => user.profile,
(newVal) => {
console.log('profile changed')
},
{ deep: true }
)
2. watchEffect:自动追踪依赖,立即执行
watchEffect(() => {
console.log('Current count:', count.value)
// 自动监听 count.value 变化
})
适用:副作用逻辑,如日志、请求、DOM 操作。
六、组件通信:defineExpose + template ref
子组件暴露方法/属性
<!-- Child.vue -->
<script setup lang="ts">
const scrollIntoView = () => {
// 滚动逻辑
}
defineExpose({
scrollIntoView
})
</script>
父组件获取子组件实例
<!-- Parent.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref<InstanceType<typeof Child> | null>(null)
const handleScroll = () => {
childRef.value?.scrollIntoView()
}
</script>
<template>
<Child ref="childRef" />
<button @click="handleScroll">滚动到视图</button>
</template>
注意:
- 必须用
defineExpose才能暴露- TS 中建议用
InstanceType<typeof Component>类型推断
七、插槽 slot:内容分发
1. 默认插槽
<!-- Layout.vue -->
<template>
<div class="layout">
<header>Header</header>
<main>
<slot></slot>
</main>
</div>
</template>
<!-- 使用 -->
<Layout>
<p>这是主体内容</p>
</Layout>
2. 具名插槽
<!-- Layout.vue -->
<slot name="header"></slot>
<slot name="sidebar"></slot>
<slot></slot>
<Layout>
<template #header>
<h1>自定义标题</h1>
</template>
<template #sidebar>
<nav>菜单</nav>
</template>
<p>主内容</p>
</template>
3. 作用域插槽(传数据给父组件)
<!-- List.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="index"></slot>
</li>
</ul>
</template>
<List>
<template #default="{ item, index }">
<span>{{ index }}: {{ item.name }}</span>
</template>
</template>
适用于可定制组件(如表格、列表)
八、Pinia 状态管理(替代 Vuex)
1. 安装与配置
npm install pinia
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
2. 创建 Store
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'Alice',
age: 20,
hobbies: ['reading'] as string[]
}),
getters: {
doubleAge: (state) => state.age * 2,
info: (state) => `${state.name}, ${state.age}岁`
},
actions: {
updateName(name: string) {
this.name = name
},
async fetchUserData() {
const res = await api.getUser()
this.$patch(res) // 批量更新
}
}
})
3. 在组件中使用
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 读取
console.log(userStore.name)
// 修改
userStore.updateName('Bob')
// 解构(保持响应式)
import { storeToRefs } from 'pinia'
const { name, age } = storeToRefs(userStore)
const { updateName } = userStore
</script>
storeToRefs:解构时保持响应式,不会丢失追踪。
九、其他常用技巧
1. onMounted, onUnmounted 等生命周期
import { onMounted, onUnmounted, onBeforeUnmount } from 'vue'
onMounted(() => {
console.log('组件挂载')
})
onBeforeUnmount(() => {
// 清理定时器、事件监听
})
2. nextTick:等待 DOM 更新后执行
import { nextTick } from 'vue'
await nextTick()
// DOM 已更新
3. defineOptions(可选,用于 defineSetup 辅助)
<script setup lang="ts">
defineOptions({
name: 'MyComponent',
inheritAttrs: false
})
</script>
十、项目结构建议(推荐)
src/
├── components/ # 通用组件
├── views/ # 页面组件
├── composables/ # 自定义 Hook(useXXX)
├── stores/ # Pinia stores
├── utils/ # 工具函数
├── api/ # 接口请求
├── types/ # TS 类型定义
├── assets/ # 静态资源
└── App.vue / main.ts
总结:Vue 3 + TS 核心要点速查表
| 功能 | 推荐方式 | 说明 |
|---|---|---|
| 响应式数据 | ref / reactive | 基本类型用 ref,对象用 reactive |
| 父子传值 | defineProps + defineEmits | TS 类型安全 |
| 计算属性 | computed | 缓存、性能好 |
| 侦听变化 | watch / watchEffect | 精准或自动依赖 |
| 获取子组件 | ref + defineExpose | 需暴露才能访问 |
| 内容分发 | slot | 支持默认、具名、作用域插槽 |
| 状态管理 | Pinia | 简洁、类型友好、模块化 |
| 生命周期 | onMounted 等 | Composition API 风格 |
| 最佳实践 | script setup + TS | 减少模板代码,类型安全 |

1718

被折叠的 条评论
为什么被折叠?



