ts中定义参数变量类型 enum interface type有什么区别
-
一个enum(枚举)的定义 -
一个interface(接口)的定义 -
一个type(类型别名)的定义
使用建议
场景
推荐使用
原因
对象结构定义
interface
可扩展、可继承、IDE 支持更好
联合/交叉类型
type
更灵活,支持复杂类型操作
固定值集合
enum
提供类型安全的值集合
函数类型
interface或 type
根据是否需要重载决定
元组类型
type
更简洁的语法
类实现
interface
明确的契约关系
工具类型
type
支持条件类型、映射类型
实际选择指南
-
优先使用
interface:当需要描述对象结构,且可能需要扩展时 -
使用
type:当需要联合类型、交叉类型或复杂类型操作时 -
使用
enum:当需要一组命名的常量,且这些常量在运行时需要被访问时 -
两者都可时:根据团队约定或个人偏好选择,保持项目一致
['','111'].filter(Boolean)是什么意思
.filter(Boolean) 用来去掉数组里的假值。
Boolean 作为回调时,会把每个元素转成布尔值:
-
假值(falsy)→ false → 被过滤掉
-
真值(truthy)→ true → 保留
// 逗号分隔的字符串"tag1,,tag2,".split(',') // → ['tag1', '', 'tag2', '']// 加上 .filter(Boolean)"tag1,,tag2,".split(',').filter(Boolean) // → ['tag1', 'tag2']
等价写法
.filter(Boolean)// 等价于.filter(item => Boolean(item))// 等价于.filter(item => !!item)// 等价于.filter(item => item)
row: Record
row: Record<string, unknown> 是 TypeScript 的类型注解,用来描述 row 的类型。
含义:
Record<K, V>:TypeScript 内置工具类型,表示“键为 K、值为 V 的对象”。Record<string, unknown>:键是 string,值是 unknown 的对象。
- Record<K, V>:TypeScript 内置工具类型,表示“键为 K、值为 V 的对象”。
- Record<string, unknown>:键是 string,值是 unknown 的对象。
等价写法
// 这两种写法等价
row: Record<string, unknown>
// 等价于
row: { [key: string]: unknown }
实际含义
row 是一个对象,满足:
-
键:任意字符串(如 "id", "source", "name" 等)
-
值:任意类型(unknown 表示“类型未知,使用前需要做类型检查”)
为什么用 unknown 而不是 any?
// unknown:更安全,使用前需要先断言或收窄类型row.id // 报错:Object is of type 'unknown'(row as TermItem).id // 需要先断言// any:不检查,容易出错row: Record<string, any>row.id // 不报错,但可能运行时出错
unknown 要求你在使用前显式做类型检查或断言,更安全。
在「选中/取消选中」场景里,用 Set 比用数组更合适。
1. 查找更快
// Set:O(1)
selectedIds.has(id)
// 数组:O(n)
selectedIds.includes(id)
判断某个 id 是否被选中时,Set.has() 是常数时间,数组的 includes() 需要遍历。
2. 不会重复
// Set:自动去重
selectedIds.add(1)
selectedIds.add(1) // 不会重复
// selectedIds.size === 1
// 数组:需要手动去重
selectedIds.push(1)
selectedIds.push(1) // 会重复
// 需要 if (!selectedIds.includes(id)) selectedIds.push(id)
Set 天然保证每个 id 只出现一次。
3. 增删更直观
// Set
selectedIds.add(id)
selectedIds.delete(id)
// 数组:需要找下标再 splice
const idx = selectedIds.indexOf(id)
if (idx > -1) selectedIds.splice(idx, 1)
4. 什么时候用数组更合适?
-
需要保持顺序
-
需要按索引访问
-
需要 map、filter 等数组方法
defineEmits<{ deleted: [] }>();
defineEmits<{ deleted: [] }>() 是 Vue 3 中声明组件会发出的事件的方式。
含义
-
defineEmits:Vue 3 的编译器宏,用来定义组件会 emit 的事件。
-
<{ deleted: [] }>:TypeScript 泛型,描述事件名和参数类型。
-
deleted:事件名。
-
[]:事件 payload 类型,[] 表示该事件没有参数。
等价写法
// 无参数
defineEmits<{ deleted: [] }>()
// 有参数
defineEmits<{ deleted: []; updated: [id: number, name: string] }>()
const emit = defineEmits<{ deleted: [] }>();
// 触发 deleted 事件,不传参
emit('deleted');
作用
-
类型检查:调用 emit('deleted', 123) 会报错,因为 deleted 定义为无参数。
-
文档:一眼能看出组件会发出哪些事件及参数类型。
-
IDE 提示:能获得更准确的补全和类型提示。
defineProps在TS中怎么写
// 接口式声明(推荐)
<script setup lang="ts">
interface Props {
title: string
count?: number
items: Array<{ id: number; name: string }>
}
const props = defineProps<Props>()
</script>
// 特点:通过接口定义类型,支持复杂嵌套结构// 优势:IDE 自动补全、类型安全检查
// 泛型式声明
<script setup lang="ts">
const props = defineProps<{
modelValue: string
disabled?: boolean
onChange: (value: string) => void
}>()
</script>
// 特点:直接在泛型参数中声明类型// 适用场景:简单 props 结构
1. 默认值处理 使用 withDefaults
<script setup lang="ts">
interface Props {
title: string
count?: number
theme: 'light' | 'dark' = 'light'
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
theme: 'dark'
})
</script>
// 注意:对象/数组需用工厂函数返回默认值// 优势:类型推导 + 默认值统一管理
2. 响应式解构
<script setup lang="ts">
import { toRefs } from 'vue'
const props = defineProps<{ user: { name: string } }>()
const { user } = toRefs(props) // 保持响应式
</script>
高级类型场景 1.联合类型
<script setup lang="ts">
type Status = 'loading' | 'success' | 'error'
const props = defineProps<{
status: Status
data: string | number
}>()
</script>
2. 泛型组件
<script setup lang="ts">
interface TableProps<T> {
data: T[]
columns: Array<keyof T>
}
const props = defineProps<TableProps<{ id: number; name: string }>>()
</script>
3.类型守卫
<script setup lang="ts">
const isString = (val: unknown): val is string => typeof val === 'string'
const props = defineProps<{ value: unknown }>()
if (isString(props.value)) {
// 类型收窄后可用字符串方法
console.log(props.value.toUpperCase())
}
</script>
默认值规范:
基本类型直接赋值
复杂类型用工厂函数
withDefaults(defineProps<Props>(), {
config: () => ({ timeout: 3000 })
})
组合式暴露:
defineExpose({
refresh: () => fetch(props.id)
})
文档注释:
/**
* @default 100
* @example 200
*/
const props = defineProps<{ limit: number }>()
defineModel在TS中怎么写
1. 泛型类型声明(推荐)
<script setup lang="ts">
// 基础类型
const modelValue = defineModel<string>()
// 必填类型
const requiredValue = defineModel<string>({ required: true })
// 联合类型
const status = defineModel<'active' | 'disabled'>()
</script>
// 特点:通过泛型参数 <T>明确类型// 优势:IDE 自动补全、类型安全检查
2. 复杂类型声明
interface User {
id: number
name: string
}
const user = defineModel<User>({
default: () => ({ id: 0, name: '' })
})
// 特点:通过泛型参数 <T>明确类型// 优势:IDE 自动补全、类型安全检查
高级类型场景 1. 默认值设置
<script setup lang="ts">
// 基本类型默认值
const count = defineModel<number>({ default: 0 })
// 对象类型默认值(需工厂函数)
const config = defineModel<{ timeout: number }>({
default: () => ({ timeout: 3000 })
})
</script>
// 注意:对象/数组需用工厂函数避免引用共享
2. 类型校验
<script setup lang="ts">
const email = defineModel<string>({
validator: (val): val is string => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)
})
// 或使用内置类型校验
const age = defineModel<number>({
type: Number,
required: true
})
</script>
3. 修饰符处理
<script setup lang="ts">
const [value, modifiers] = defineModel<string, 'trim'>({
set(val) {
return modifiers.trim ? val.trim() : val
}
})
// 父组件使用
// <Child v-model.trim="inputValue" />
</script>
三、多 Model 支持
<script setup lang="ts">
// 多个 v-model 绑定
const firstName = defineModel<string>('firstName')
const lastName = defineModel<string>('lastName')
// 父组件使用
// <Form v-model:firstName="user.firstName" v-model:lastName="user.lastName" />
</script>
四、类型守卫与转换
<script setup lang="ts">
// 类型守卫
const isString = (val: unknown): val is string => typeof val === 'string'
const [rawValue, modifiers] = defineModel<string>({
set(val) {
if (modifiers.uppercase) {
return isString(val) ? val.toUpperCase() : val
}
return val
}
})
</script>
五、最佳实践总结
// 推荐
const model = defineModel<string>()
// 不推荐
const model = defineModel()
// 基本类型
defineModel<number>({ default: 0 })
// 复杂类型
defineModel<{ items: string[] }>({
default: () => ({ items: [] })
})
defineExpose({
modelValue: model.value
})
shims-vue.d.ts文件
这个文件负责为 Vue 和项目里的自定义用法提供 TypeScript 类型支持,保证类型检查和 IDE 提示正常工作。
declare module 'vue' {
export interface GlobalComponents {
ElSelect: (typeof import('element-plus'))['ElSelect'];
SvgIcon: (typeof import('@/packages/component'))['SvgIcon'];
}
}
-
declare module 'vue':对 Vue 模块做类型扩展,而不是新建一个模块。
-
GlobalComponents:Vue 中用于声明全局组件类型的接口,模板里的组件会在这里查找类型。
-
每个属性:把组件名映射到对应组件的类型,例如 ElSelect 对应 Element Plus 的 ElSelect 类型
import { defineStore } from 'pinia';
Pinia是Vue 3推荐的状态管理库,替代了Vuex。defineStore是用来定义Store的核心函数。
一、基础语法与参数
import { defineStore } from 'pinia';
// 方式 1:选项式 API(类似 Vue 2 的 Options API)
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: { doubleCount: (state) => state.count * 2 },
actions: { increment() { this.count++; } }
});
二、核心特性解析
1. 状态管理(State)
-
选项式:通过
state函数返回初始状态对象。 -
Composition API:直接使用
ref或reactive定义响应式状态。 -
特点:状态自动持久化到 Vue 的响应式系统中,无需手动
.value访问。
2. 计算属性(Getters)
-
选项式:在
getters中定义,类似 Vue 的计算属性。 -
Composition API:通过
computed创建,自动缓存依赖。getters: { filteredItems: (state) => state.items.filter(item => item.active) }
3. 方法(Actions)
-
选项式:在
actions中定义方法,通过this访问状态。 -
Composition API:直接定义函数,可包含异步逻辑。
actions: { async fetchData() { const response = await fetch('/api/data'); this.data = await response.json(); } }
三、在组件中使用 Store
import { useCounterStore } from '@/stores/counter';
export default {
setup() {
const store = useCounterStore();
return { store };
}
};
访问状态与方法:
-
直接通过
store.count访问状态。 -
调用
store.increment()触发 Action。 -
注意:直接解构会破坏响应式,需使用
storeToRefs:import { storeToRefs } from 'pinia'; const { count } = storeToRefs(store);
四、高级功能
1. 模块化与多 Store
-
每个 Store 独立管理状态,支持按功能拆分(如
userStore、cartStore)。 -
通过
import { useUserStore } from '@/stores/user'按需引入。
2. 插件扩展
-
通过插件实现持久化、日志记录等功能:
pinia.use(({ store }) => { store.$router = router; // 注入全局依赖 });
五、最佳实践
-
命名规范:Store 的 ID 建议与功能相关(如
'auth'),返回函数建议以use开头(如useAuthStore)。 -
类型安全:结合 TypeScript 定义类型,增强代码提示和检查:
interface UserState { name: string; age: number; } export const useUserStore = defineStore('user', { state: (): UserState => ({ name: '', age: 0 }) });
storeToRefs
使用场景
解构复杂状态
-
当需要从 Store 中提取多个状态或计算属性时,避免直接解构导致响应性失效。
import { storeToRefs } from 'pinia'; const store = useUserStore(); const { name, age } = storeToRefs(store); // name 和 age 是 ref 对象
2. 模板中直接使用
在模板中无需 .value即可访问响应式数据(Vue 自动解包)
<template>
<p>{{ name }}</p> <!-- 自动解包为 name.value -->
</template>
import { storeToRefs } from 'pinia';
import { useStore } from '@/stores/storeName';
const store = useStore();
const { stateProp1, getterProp } = storeToRefs(store);
this.$patch
在 Vue3 + TypeScript 中,this.$patch是用于批量更新响应式对象属性的核心方法,其设计目的是优化多次状态修改时的性能并保证原子性。以下是其详细用法与最佳实践:
一、基础用法
1. 对象式批量更新
// 假设 state 是 reactive 对象
const state = reactive({
count: 0,
name: 'Alice',
items: [] as string[]
});
// 批量修改多个属性
this.$patch({
count: 1,
name: 'Bob',
items: ['item1', 'item2']
});
特点:所有修改合并为单次响应式更新,仅触发一次计算属性或侦听器
2. 函数式动态更新
this.$patch((state: typeof state) => {
state.count += 1;
state.items.push('newItem');
// 可基于当前状态计算新值
state.name = `User${state.count}`;
});
优势:支持依赖当前状态的动态逻辑,避免中间状态暴露
二、与直接赋值的对比
场景
直接赋值
**$patch**
单属性修改
state.count = 1
不适用
多属性修改
多次赋值(多次触发更新)
单次调用(合并为一次更新)
依赖当前状态
需手动获取旧值(如 count + 1)
直接在函数内使用 state.count
性能
可能低效(多次触发)
高效(批量处理)
// 直接赋值(触发两次更新)
state.count++;
state.items.push('item');
// $patch(触发一次更新)
this.$patch({
count: state.count + 1,
items: [...state.items, 'item']
});
**三、TypeScript 类型支持
**1. 显式类型声明
interface State {
count: number;
name: string;
items: string[];
}
const state = reactive<State>({
count: 0,
name: '',
items: []
});
// 函数式更新时明确类型
this.$patch((state: State) => {
state.count = 1;
});
2. 自动类型推断
Vue3 的响应式系统会自动推断 state的类型,但复杂场景建议显式声明。
四、适用场景
-
表单多字段提交
同时更新多个表单字段时,避免多次渲染:
this.$patch({ username: 'user123', email: 'user@example.com', age: 25 });
2.复杂状态逻辑
需要基于当前状态计算新值时:
this.$patch(state => {
state.total = state.items.reduce((sum, item) => sum + item.price, 0);
});