让我帮你理解 TypeScript 代码,通过对比来学习:

  1. 基础类型定义
// 没用 TS 时
let price = 88
let name = '红烧肉'

// 用了 TS 后
let price: number = 88         // 意思是:price 只能是数字
let name: string = '红烧肉'    // 意思是:name 只能是字符串
  1. 对象类型定义
// 没用 TS 时
const dish = {
    name: '红烧肉',
    price: 88,
    isSpicy: true
}

// 用了 TS 后
interface Dish {              // interface 相当于定义一个"模板"
    name: string;            // 规定 name 必须是字符串
    price: number;          // 规定 price 必须是数字
    isSpicy: boolean;      // 规定 isSpicy 必须是布尔值
}

const dish: Dish = {        // 告诉 TS:这个对象必须符合 Dish 的格式
    name: '红烧肉',
    price: 88,
    isSpicy: true
}
  1. 数组类型定义
// 没用 TS 时
const prices = [88, 66, 128]
const dishes = ['红烧肉', '青椒肉丝']

// 用了 TS 后
const prices: number[] = [88, 66, 128]     // 数组里只能放数字
const dishes: string[] = ['红烧肉', '青椒肉丝']  // 数组里只能放字符串
  1. 函数类型定义
// 没用 TS 时
function calculateTotal(price, quantity) {
    return price * quantity
}

// 用了 TS 后
function calculateTotal(price: number, quantity: number): number {
    return price * quantity
}
// 意思是:
// 1. price 必须是数字
// 2. quantity 必须是数字
// 3. 返回值必须是数字
  1. Vue 组件中的应用
// 没用 TS 时
const count = ref(0)
const message = ref('')

// 用了 TS 后
const count = ref<number>(0)      // count 只能是数字
const message = ref<string>('')   // message 只能是字符串
  1. 事件处理
// 没用 TS 时
function handleClick(event) {
    console.log(event.target.value)
}

// 用了 TS 后
function handleClick(event: MouseEvent): void {
    const target = event.target as HTMLInputElement  // 告诉 TS:这是一个输入框
    console.log(target.value)
}

学习建议:

  1. 循序渐进

    • 先理解基础类型(string, number, boolean)
    • 再学习对象和数组的类型定义
    • 最后学习接口和泛型
  2. 从简单开始
// 第一步:添加基础类型
let price: number = 88
let name: string = '红烧肉'

// 第二步:添加对象类型
interface MenuItem {
    name: string
    price: number
}

// 第三步:添加数组类型
const menu: MenuItem[] = [
    { name: '红烧肉', price: 88 },
    { name: '青椒肉丝', price: 66 }
]
  1. 使用 VS Code
  • 安装 TypeScript 插件
  • 鼠标悬停可以看到类型提示
  • 代码补全更智能
  1. 常见错误理解
// ❌ 错误示例
const price: number = "88"  // 错误:字符串不能赋值给数字类型

// ✅ 正确示例
const price: number = 88    // 正确:数字类型

记住:

  • TypeScript 主要是帮助我们避免错误
  • 类型定义就像是"约定"
  • 代码看起来复杂了,但更安全了
  • 开发时会有更好的提示

如果你觉得某段代码看不懂,可以:

  1. 去掉类型声明,看纯 JavaScript 逻辑
  2. 一点点加入类型,理解每个类型的作用
  3. 使用 VS Code 的类型提示功能

categoriesstate.categories 在 Vue3 中有重要区别:

  1. 响应式处理方式
// 直接定义
const categories = ref([...])  // 使用 ref
// 或
const categories = reactive([...])  // 使用 reactive

// 通过 state 定义
const state = reactive({
    categories: [...],
    otherData: ...
})
  1. 访问方式
// categories 的访问
categories.value  // 如果是 ref,需要 .value
// 或
categories  // 如果是 reactive,直接访问

// state.categories 的访问
state.categories  // 永远是直接访问,不需要 .value
  1. 数据组织
// categories 方式
const categories = reactive([...])
const currentIndex = ref(0)
const otherData = ref('')

// state 方式(推荐)
const state = reactive({
    categories: [...],
    currentIndex: 0,
    otherData: ''
})
  1. 代码示例
// 使用 categories
const categories = reactive([])
function updateCategories() {
    categories.push(newItem)  // 直接修改
}

// 使用 state.categories
const state = reactive({
    categories: []
})
function updateCategories() {
    state.categories.push(newItem)  // 通过 state 修改
}

使用 state.categories 的优势:

  1. 统一的数据管理
  2. 更清晰的数据结构
  3. 避免多个响应式变量散落
  4. 更容易进行状态管理

建议使用 state.categories 的方式,因为它更符合 Vue3 的最佳实践。

对比一下两种写法:

原始代码:

const checkedItemsCount = computed(() => {
    let total = 0
    categories.map(category => {
        total += category.items.filter(item => item.check).length;
    });
    return total
});

优化后的代码:

const checkedItemsCount = computed(() => {
    return state.categories.reduce((total, category) => 
        total + (category.items?.filter(item => item.check)?.length ?? 0), 0
    )
})

主要区别:

  1. 空值安全性
  • 原始代码:直接访问 category.items,如果 itemsundefined 会报错
  • 优化代码:使用 ?.?? 操作符处理可能的空值,更安全
  1. 性能差异
  • 原始代码:使用 map 但没有使用其返回值,实际上应该用 forEach
  • 优化代码:使用 reduce 直接进行累加,减少了中间变量
  1. 函数式编程
  • 原始代码:使用命令式编程,维护外部变量 total
  • 优化代码:使用函数式编程,无副作用,更容易测试和维护
  1. 代码健壮性
// 原始代码可能出错的情况:
categories = undefined  // 报错
category.items = undefined  // 报错

// 优化代码能处理的情况:
state.categories = undefined  // 返回 0
category.items = undefined  // 返回 0
  1. 内存使用
  • 原始代码:需要额外的 total 变量
  • 优化代码:直接在 reduce 过程中计算,无需额外变量

建议使用优化后的写法,因为它:

  • 更安全(处理了空值情况)
  • 更高效(减少了不必要的操作)
  • 更易维护(函数式编程风格)
  • 更简洁(代码更少但功能更强)

总结一下 TypeScript 在 Vue3 中的一些固定写法:

  1. Props 定义
// 固定写法 1:使用 defineProps
defineProps<{
    title: string
    count: number
    isActive?: boolean  // 可选属性
}>()

// 固定写法 2:带默认值的 props
withDefaults(defineProps<{
    title: string
    count: number
    isActive?: boolean
}>(), {
    isActive: false
})
  1. Emits 定义
// 固定写法:定义事件
const emit = defineEmits<{
    (e: 'update', value: string): void
    (e: 'delete'): void
}>()

// 使用方式
emit('update', newValue)
emit('delete')
  1. ref 定义
// 固定写法:基础类型
const count = ref<number>(0)
const message = ref<string>('')
const isLoading = ref<boolean>(false)

// 固定写法:DOM 元素
const divEl = ref<HTMLDivElement | null>(null)
const inputEl = ref<HTMLInputElement | null>(null)
const formEl = ref<HTMLFormElement | null>(null)
  1. reactive 定义
// 固定写法:对象类型
interface State {
    count: number
    message: string
    user: {
        name: string
        age: number
    }
}

const state = reactive<State>({
    count: 0,
    message: '',
    user: {
        name: '',
        age: 0
    }
})
  1. 计算属性
// 固定写法:computed
const doubleCount = computed<number>(() => count.value * 2)

// 带 get/set 的计算属性
const fullName = computed<string>({
    get() {
        return `${firstName.value} ${lastName.value}`
    },
    set(newValue: string) {
        [firstName.value, lastName.value] = newValue.split(' ')
    }
})
  1. 事件处理函数
// 固定写法:事件处理
function handleClick(event: MouseEvent): void {
    console.log(event.target)
}

function handleInput(event: Event): void {
    const target = event.target as HTMLInputElement
    console.log(target.value)
}

function handleSubmit(event: SubmitEvent): void {
    event.preventDefault()
}
  1. 组件实例类型
// 固定写法:组件实例
import { ComponentPublicInstance } from 'vue'

const componentRef = ref<ComponentPublicInstance | null>(null)

// 自定义组件类型
interface CustomComponent {
    reload(): void
    clear(): void
}
const customRef = ref<CustomComponent | null>(null)
  1. 生命周期钩子
// 固定写法:生命周期
onMounted((): void => {
    console.log('mounted')
})

onBeforeUnmount((): void => {
    console.log('before unmount')
})
  1. watch/watchEffect
// 固定写法:watch
watch(count, (newValue: number, oldValue: number): void => {
    console.log(newValue, oldValue)
})

// 固定写法:watchEffect
watchEffect((): void => {
    console.log(count.value)
})
  1. 类型断言
// 固定写法:类型断言
const element = event.target as HTMLInputElement
const userInfo = reactive({}) as { name: string, age: number }
  1. 泛型组件
// 固定写法:泛型组件
interface GenericProps<T> {
    items: T[]
    selected: T
}

defineProps<GenericProps<{ id: number, name: string }>>()

这些是最常用的固定写法,记住这些可以帮助你:

  • 快速开始 TypeScript + Vue3 开发
  • 避免常见的类型错误
  • 提高开发效率

建议:

  1. 先掌握这些基础的固定写法
  2. 在实际项目中逐步应用
  3. 随着深入学习再扩展更复杂的用法
  4. 善用 IDE 的代码提示功能

让我通过实例来说明 TypeScript 如何处理 HTML 元素:

  1. DOM 元素类型
// 基础元素引用
const menuContainer = ref<HTMLDivElement | null>(null)
const priceInput = ref<HTMLInputElement | null>(null)
const foodImage = ref<HTMLImageElement | null>(null)

// 在模板中使用
<template>
    <div ref="menuContainer">
        <input ref="priceInput" type="number" />
        <img ref="foodImage" :src="imageUrl" />
    </div>
</template>
  1. 事件处理
// 事件类型定义
function handleClick(event: MouseEvent): void {
    console.log(event.clientX, event.clientY)
}

function handleInput(event: InputEvent): void {
    const target = event.target as HTMLInputElement
    console.log(target.value)
}

// 在模板中使用
<template>
    <button @click="handleClick">点击</button>
    <input @input="handleInput" />
</template>
  1. 自定义事件
// 定义事件类型
interface OrderEvent {
    orderId: string
    dishes: MenuItem[]
}

// 事件处理函数
function handleOrder(event: OrderEvent): void {
    console.log(`订单 ${event.orderId} 包含 ${event.dishes.length} 个菜品`)
}

// 发出事件
const emit = defineEmits<{
    (event: 'order', payload: OrderEvent): void
    (event: 'cancel'): void
}>()
  1. 表单元素处理
interface FormElements extends HTMLFormControlsCollection {
    username: HTMLInputElement
    password: HTMLInputElement
}

interface OrderForm extends HTMLFormElement {
    readonly elements: FormElements
}

function handleSubmit(event: SubmitEvent) {
    event.preventDefault()
    const form = event.target as OrderForm
    
    const username = form.elements.username.value
    const password = form.elements.password.value
}

// 在模板中使用
<template>
    <form @submit="handleSubmit">
        <input name="username" type="text" />
        <input name="password" type="password" />
        <button type="submit">提交</button>
    </form>
</template>
  1. ref 和 reactive 的类型定义
// ref 的类型定义
const selectedDish = ref<MenuItem | null>(null)
const dishList = ref<MenuItem[]>([])

// reactive 的类型定义
interface State {
    cart: MenuItem[]
    totalPrice: number
    isLoading: boolean
}

const state = reactive<State>({
    cart: [],
    totalPrice: 0,
    isLoading: false
})
  1. 组件 Props 类型定义
// 定义 Props 接口
interface MenuProps {
    category: string
    dishes: MenuItem[]
    isActive?: boolean // 可选属性
}

// 在组件中使用
defineProps<MenuProps>()

// 或者使用 withDefaults
withDefaults(defineProps<MenuProps>(), {
    isActive: false
})
  1. 常见的 HTML 元素事件类型
// 鼠标事件
function handleMouseEvent(event: MouseEvent): void {
    console.log(event.clientX, event.clientY)
}

// 键盘事件
function handleKeyEvent(event: KeyboardEvent): void {
    if (event.key === 'Enter') {
        // 处理回车键
    }
}

// 触摸事件
function handleTouchEvent(event: TouchEvent): void {
    const touch = event.touches[0]
    console.log(touch.clientX, touch.clientY)
}

// 在模板中使用
<template>
    <div 
        @mouseenter="handleMouseEvent"
        @keydown="handleKeyEvent"
        @touchstart="handleTouchEvent"
    >
        交互区域
    </div>
</template>
  1. 自定义组件的 ref 类型
// 定义组件接口
interface MenuComponent {
    reload: () => void
    clear: () => void
}

// 使用组件 ref
const menuRef = ref<MenuComponent | null>(null)

// 调用组件方法
function refreshMenu() {
    menuRef.value?.reload()
}

// 在模板中使用
<template>
    <menu-component ref="menuRef" />
</template>
  1. 错误处理示例
// ❌ 错误示例
const element = document.querySelector('.menu') // 类型推断为 Element | null

// ✅ 正确示例
const element = document.querySelector('.menu') as HTMLDivElement
// 或者
const element = document.querySelector<HTMLDivElement>('.menu')

使用建议:

  1. 总是为 ref 和事件处理函数定义具体类型
  2. 使用 TypeScript 内置的 DOM 类型定义
  3. 避免使用 any 类型
  4. 利用 IDE 的类型提示功能
  5. 合理使用类型断言(as)

这些类型定义可以帮助你:

  • 在开发时捕获潜在错误
  • 提供更好的代码提示
  • 提高代码的可维护性
  • 使代码更加健壮