Vue3+TS

22 阅读3分钟

ts中定义参数变量类型 enum interface type有什么区别

  • 一个enum(枚举)的定义

  • 一个interface(接口)的定义

  • 一个type(类型别名)的定义

使用建议

场景

推荐使用

原因

对象结构定义

interface

可扩展、可继承、IDE 支持更好

联合/交叉类型

type

更灵活,支持复杂类型操作

固定值集合

enum

提供类型安全的值集合

函数类型

interfacetype

根据是否需要重载决定

元组类型

type

更简洁的语法

类实现

interface

明确的契约关系

工具类型

type

支持条件类型、映射类型

实际选择指南

  1. 优先使用 interface:当需要描述对象结构,且可能需要扩展时

  2. 使用 type:当需要联合类型、交叉类型或复杂类型操作时

  3. 使用 enum:当需要一组命名的常量,且这些常量在运行时需要被访问时

  4. 两者都可时:根据团队约定或个人偏好选择,保持项目一致

['','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 的对象。

  1. Record<K, V>:TypeScript 内置工具类型,表示“键为 K、值为 V 的对象”。
  2. Record<string, unknown>:键是 string,值是 unknown 的对象。

等价写法

// 这两种写法等价
row: Record<string, unknown>
// 等价于
row: { [key: string]: unknown }

实际含义

row 是一个对象,满足:

  1. 键:任意字符串(如 "id", "source", "name" 等)

  2. 值:任意类型(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');

作用

  1. 类型检查:调用 emit('deleted', 123) 会报错,因为 deleted 定义为无参数。

  2. 文档:一眼能看出组件会发出哪些事件及参数类型。

  3. 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:直接使用 refreactive定义响应式状态。

  • 特点:状态自动持久化到 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 独立管理状态,支持按功能拆分(如 userStorecartStore)。

  • 通过 import { useUserStore } from '@/stores/user'按需引入。

2. 插件扩展

  • 通过插件实现持久化、日志记录等功能:

    pinia.use(({ store }) => { store.$router = router; // 注入全局依赖 });

五、最佳实践

  1. 命名规范:Store 的 ID 建议与功能相关(如 'auth'),返回函数建议以 use开头(如 useAuthStore)。

  2. 类型安全:结合 TypeScript 定义类型,增强代码提示和检查:

    interface UserState {
      name: string;
      age: number;
    }
    export const useUserStore = defineStore('user', {
      state: (): UserState => ({ name: '', age: 0 })
    });
    

storeToRefs

使用场景

解构复杂状态

  1. 当需要从 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的类型,但复杂场景建议显式声明。

四、适用场景

  1. 表单多字段提交

    同时更新多个表单字段时,避免多次渲染:

    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);
});