vue3+ts,技巧
·
1. 插槽语法统一(v-slot)
Vue3 中统一使用v-slot指令,替代了之前的slot属性写法:
vue
<!-- 父组件 -->
<template>
<ChildComponent>
<!-- 具名插槽 -->
<v-slot:default>默认插槽内容</v-slot:default>
<!-- 具名插槽 -->
<v-slot:header>头部内容</v-slot:header>
<!-- 作用域插槽 -->
<v-slot:item="slotProps">
{{ slotProps.data }}
</v-slot:item>
</ChildComponent>
</template>
<!-- 子组件 ChildComponent.vue -->
<template>
<div>
<slot name="header"></slot>
<slot></slot>
<slot name="item" :data="message"></slot>
</div>
</template>
<script setup>
const message = "来自子组件的数据"
</script>
2. Render 函数变化
Vue3 的 render 函数更贴合组合式 API 风格:
vue
<template>
<div>
<render-component />
</div>
</template>
<script setup>
import { h } from 'vue'
// 定义一个使用render函数的组件
const RenderComponent = {
render() {
// 返回虚拟DOM节点
return h('div', {
class: 'render-example',
onClick: () => console.log('点击了')
}, [
h('h3', '这是render函数创建的元素'),
h('p', 'Vue3的render函数更简洁')
])
}
}
</script>
3. 全局 API 使用方式
Vue3 通过createApp创建应用实例,避免全局污染:
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 创建应用实例
const app = createApp(App)
// 使用插件
app.use(router)
app.use(store)
// 全局注册组件
import GlobalComponent from './components/GlobalComponent.vue'
app.component('GlobalComponent', GlobalComponent)
// 全局注册指令
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 挂载应用
app.mount('#app')
4. Teleport 组件使用
teleport可以将组件内容渲染到指定 DOM 节点:
vue
<template>
<div>
<button @click="showModal = true">打开模态框</button>
<teleport to="body">
<div v-if="showModal" class="modal-backdrop">
<div class="modal">
<h3>这是一个模态框</h3>
<p>内容会被传送到body节点下</p>
<button @click="showModal = false">关闭</button>
</div>
</div>
</teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style>
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal {
background: white;
padding: 20px;
border-radius: 5px;
}
</style>
5. Composition API 示例
Composition API 提供了更灵活的逻辑组织方式:
vue
<template>
<div>
<h3>用户列表</h3>
<div v-if="loading">加载中...</div>
<div v-if="error">{{ error }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
<button @click="fetchUsers">刷新用户</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 定义响应式数据
const users = ref([])
const loading = ref(false)
const error = ref(null)
// 定义逻辑函数
const fetchUsers = async () => {
try {
loading.value = true
error.value = null
const response = await fetch('https://api.example.com/users')
const data = await response.json()
users.value = data
} catch (err) {
error.value = '获取用户失败,请重试'
console.error(err)
} finally {
loading.value = false
}
}
// 生命周期钩子
onMounted(() => {
fetchUsers()
})
</script>
********************关于TS***************************
如何用ts在vue3中自定义组件
1. 基础组件定义(使用 <script setup lang="ts">)
这是 Vue 3 推荐的方式,结合了 <script setup> 的简洁性和 TypeScript 的类型安全。
<template>
<button
class="custom-button"
:class="{ 'is-disabled': disabled, 'is-primary': type === 'primary' }"
@click="$emit('click')"
:disabled="disabled"
>
<slot />
</button>
</template>
<script setup lang="ts">
// 定义组件 props 类型
const props = defineProps<{
// 按钮类型,可选值:default/primary
type?: 'default' | 'primary'
// 是否禁用
disabled?: boolean
// 按钮尺寸
size?: 'small' | 'medium' | 'large'
}>()
// 定义组件 emits 类型
const emit = defineEmits<{
// 点击事件
(e: 'click'): void
// 自定义事件,携带数字参数
(e: 'custom-event', value: number): void
}>()
// 示例方法:触发自定义事件
const handleCustomEvent = () => {
if (!props.disabled) {
emit('custom-event', Math.random())
}
}
</script>
<style scoped>
.custom-button {
padding: 8px 16px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
.custom-button.is-primary {
background-color: #42b983;
color: white;
border-color: #42b983;
}
.custom-button.is-disabled {
cursor: not-allowed;
opacity: 0.7;
}
</style>
2. 使用 defineComponent 定义组件(适用于更复杂的场景)
对于需要更复杂逻辑的组件,可以使用 defineComponent 显式定义组件选项。
<template>
<div class="card" :style="cardStyle">
<div class="card-header" v-if="title">
<h3>{{ title }}</h3>
</div>
<div class="card-body">
<slot />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
// 定义 props 接口
interface CardProps {
// 卡片标题
title?: string
// 是否有阴影
shadow?: boolean
// 自定义样式
customStyle?: Record<string, string>
}
// 定义 emits 接口
interface CardEmits {
(e: 'header-click'): void
}
export default defineComponent<CardProps, CardEmits>({
name: 'Card', // 显式指定组件名(利于调试和 IDE 提示)
props: {
title: {
type: String,
default: ''
},
shadow: {
type: Boolean,
default: false
},
customStyle: {
type: Object as () => Record<string, string>,
default: () => ({})
}
},
emits: ['header-click'],
setup(props, { emit }) {
// 计算属性(带类型推断)
const cardStyle = computed(() => ({
boxShadow: props.shadow ? '0 2px 8px rgba(0,0,0,0.1)' : 'none',
...props.customStyle
}))
// 处理标题点击
const handleHeaderClick = () => {
emit('header-click')
}
return {
cardStyle,
handleHeaderClick
}
}
})
</script>
<style scoped>
.card {
border: 1px solid #eee;
border-radius: 8px;
padding: 16px;
margin: 8px 0;
}
.card-header {
padding-bottom: 8px;
border-bottom: 1px solid #eee;
cursor: pointer;
}
</style>
3. 组件的使用示例
在父组件中使用上述自定义组件时,TypeScript 会提供完整的类型提示:
<template>
<div class="demo-page">
<!-- 使用 Button 组件 -->
<Button
type="primary"
size="medium"
@click="handleButtonClick"
@custom-event="handleCustomEvent"
>
点击我
</Button>
<!-- 使用 Card 组件 -->
<Card
title="我的卡片"
:shadow="true"
:custom-style="{ marginTop: '20px' }"
@header-click="handleCardHeaderClick"
>
<p>这是卡片内容</p>
</Card>
</div>
</template>
<script setup lang="ts">
import Button from '../components/Button.vue'
import Card from '../components/Card.vue'
const handleButtonClick = () => {
console.log('按钮被点击')
}
const handleCustomEvent = (value: number) => {
console.log('收到自定义事件值:', value)
}
const handleCardHeaderClick = () => {
console.log('卡片标题被点击')
}
</script>
关键知识点总结
-
Props 类型定义
- 使用
defineProps<{...}>进行类型约束(推荐<script setup>方式) - 支持联合类型(如
type?: 'default' | 'primary') - 支持可选属性(添加
?后缀)
- 使用
-
Emits 类型定义
- 使用
defineEmits<{...}>定义事件类型和参数 - 格式:
(e: '事件名', 参数1: 类型1, 参数2: 类型2) => void
- 使用
-
组件通信
- Props 用于父传子数据
- Emits 用于子传父事件
- 类型系统会自动检查传递的 props 和事件是否符合定义
-
高级类型技巧
- 可以提取接口(Interface)复用类型定义
- 使用
Record<string, any>定义对象类型 - 对复杂类型使用
as进行类型断言
一、高泛用性的API
(一)、ref 和 reactive 的区别
ref 和 reactive 是 Vue3 中用于创建响应式数据的核心 API,核心区别在于处理的数据类型和访问方式,实际使用中需根据数据类型选择:
1. 数据类型差异
- ref:用于处理基本类型数据(String/Number/Boolean/Null/Undefined),也可处理对象 / 数组(但内部会自动转换为 reactive 对象)。
- reactive:仅用于处理对象或数组(引用类型),无法直接处理基本类型(会报警告且不响应)。
2. 访问与修改方式
- ref:通过
.value访问 / 修改原始值(模板中自动解包,无需.value)。 - reactive:直接通过属性访问 / 修改(类似普通对象),但不能直接赋值为新对象(会丢失响应式)。
3. 实际例子
<template>
<div>
<!-- ref 在模板中自动解包,无需 .value -->
<p>ref数字: {{ count }}</p>
<p>reactive对象: {{ user.name }}</p>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
// 基本类型用 ref
const count = ref(0)
// 修改时需 .value
count.value += 1 // 正确:响应式生效
// 对象类型用 reactive
const user = reactive({ name: '张三', age: 20 })
// 直接修改属性
user.age = 21 // 正确:响应式生效
// ❌ 错误:reactive 不能直接赋值新对象(会丢失响应式)
user = { name: '李四' } // 警告 + 非响应式
// ref 处理对象(内部转为 reactive)
const info = ref({ address: '北京' })
info.value.address = '上海' // 正确:响应式生效(需 .value 访问对象)
</script>
4. 最佳实践
- 基本类型 / 单值数据(如数字、字符串)→ 用
ref。 - 复杂对象 / 数组 → 用
reactive(更符合对象的自然操作方式)。 - 注意:
reactive对象解构后会丢失响应式,需用toRefs转换(const { name } = toRefs(user))。
(二)、watch 和 watchEffect 的区别
watch 和 watchEffect 都是 Vue3 中用于监听数据变化的 API,核心区别在于监听方式和触发时机,实际使用中需根据场景选择:
1. 监听方式
- watch:显式指定监听源(需明确告诉 Vue 要监听哪个数据),且能获取新旧值。
- watchEffect:自动收集依赖(无需指定监听源,Vue 会自动追踪回调中使用的响应式数据),但无法直接获取新旧值。
2. 触发时机
- watch:默认惰性执行(初始渲染时不执行,仅在监听源变化时执行)。
- watchEffect:立即执行(初始渲染时会先执行一次,然后追踪依赖变化)。
3. 实际例子
<script setup>
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const user = ref({ name: '张三', age: 20 })
// 1. watch:显式监听,获取新旧值,惰性执行
watch(
count, // 显式指定监听源
(newVal, oldVal) => {
console.log('count变化:', newVal, oldVal) // 初始不执行,count变了才执行
}
)
// 监听对象的某个属性(需用函数返回)
watch(
() => user.value.age, // 显式指定监听 user.age
(newAge, oldAge) => {
console.log('年龄变化:', newAge, oldAge)
}
)
// 2. watchEffect:自动收集依赖,无新旧值,立即执行
watchEffect(() => {
// 自动监听 count 和 user.age(因为回调中用到了它们)
console.log('依赖变化:', count.value, user.value.age)
// 初始会执行一次(打印初始值),之后 count 或 age 变了就执行
})
</script>
4. 适用场景
-
watch:
- 需要明确知道监听的数据源(如表单提交前验证某个字段)。
- 需要获取新旧值对比(如监控数值变化幅度)。
- 需要惰性执行(如初始化时不需要执行,仅变化时执行)。
-
watchEffect:
- 依赖关系复杂,难以明确指定监听源(如同时依赖多个响应式数据)。
- 需要初始化时立即执行(如页面加载时请求数据,后续依赖变化重新请求)。
总结
refvsreactive:看数据类型,基本类型用ref(带.value),对象数组用reactive(直接操作属性)。watchvswatchEffect:看是否需要显式指定源和新旧值(选watch),还是自动收集依赖并立即执行(选watchEffect)。
更多推荐

所有评论(0)