Vue3 + TypeScript 组件开发速查表新手速成手册

关键词:Vue3 教程、TypeScript 新手、Vue 组件开发、script setup、v-model、props emits、前端入门实战

这篇是给新手的「能马上用起来」版本:少讲概念黑话,多给直接可抄的模板。 掌握这些之后可以独立写出一个类型安全、可复用、可维护的 Vue3 组件。

适读人群

  • Vue3 刚入门,想系统掌握组件开发
  • 会一点 TS,但写组件总报错
  • 想从"能跑"进阶到"规范可维护"

你将收获

  • 一套可复用的组件开发骨架
  • 高频报错的定位和修复思路
  • 发布前可自检的速查方法

建议阅读时长:15-20 分钟

目录

  • [1. 开局就别踩坑:最小推荐配置](#1. 开局就别踩坑:最小推荐配置)
  • [2. 一个组件最常见的骨架](#2. 一个组件最常见的骨架)
  • [3. Props:新手最容易写错的 4 点](#3. Props:新手最容易写错的 4 点)
  • [4. Emits:把事件写成可检查的接口](#4. Emits:把事件写成可检查的接口)
  • [5. ref / reactive / computed 怎么选](#5. ref / reactive / computed 怎么选)
  • [6. watch 与 watchEffect 一眼分清](#6. watch 与 watchEffect 一眼分清)
  • [7. v-model 组件写法(必会)](#7. v-model 组件写法(必会))
  • [8. 插槽(slot)先会用,再谈高级](#8. 插槽(slot)先会用,再谈高级)
  • [9. 模板 ref 获取 DOM(高频)](#9. 模板 ref 获取 DOM(高频))
  • [10. 父调子:defineExpose](#10. 父调子:defineExpose)
  • [11. 一个完整小案例:可复用分页组件](#11. 一个完整小案例:可复用分页组件)
  • [12. 新手高频报错对照](#12. 新手高频报错对照)
  • [13. 组件开发速查表(收藏)](#13. 组件开发速查表(收藏))
  • [14. 学习顺序建议(7 天版本)](#14. 学习顺序建议(7 天版本))
  • [15. 结语](#15. 结语)

1. 开局就别踩坑:最小推荐配置

1.1 tsconfig.json 关键项

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "jsx": "preserve",
    "types": ["vite/client"]
  }
}

strict: true 一定开。新手早期会觉得烦,但它能帮你在运行前发现一堆 bug。

1.2 env.d.ts

ts 复制代码
/// <reference types="vite/client" />

没它就容易遇到 .vue 模块识别问题。


2. 一个组件最常见的骨架

vue 复制代码
<script setup lang="ts">
interface Props {
  title: string
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  disabled: false
})

const emit = defineEmits<{
  click: [id: number]
}>()

function handleClick() {
  if (props.disabled) return
  emit('click', 1)
}
</script>

<template>
  <button :disabled="disabled" @click="handleClick">
    {{ title }}
  </button>
</template>

你要先形成这个肌肉记忆:

  • 入参defineProps
  • 出参defineEmits
  • 默认值withDefaults
  • 逻辑:函数 + 组合式 API

3. Props:新手最容易写错的 4 点

3.1 可选属性 + 默认值

ts 复制代码
interface Props {
    size?: 'sm' | 'md' | 'lg'
}

const props = withDefaults(defineProps<Props>(), {
    size: 'md'
})

有默认值就通常把该字段写成可选,类型更顺手。

3.2 对象/数组默认值

ts 复制代码
interface Props {
    tags?: string[]
}

const props = withDefaults(defineProps<Props>(), {
    tags: () => []
})

对象和数组建议用工厂函数,避免引用共享。

3.3 不要改 props

props 是只读的。要改就拷贝一份:

ts 复制代码
const localTags = ref([...(props.tags ?? [])])

3.4 string | undefined 处理

ts 复制代码
const safeTitle = computed(() => props.title ?? '默认标题')

4. Emits:把事件写成"可检查"的接口

ts 复制代码
const emit = defineEmits<{
    submit: [form: { name: string; age: number }]
    cancel: []
}>()

使用:

ts 复制代码
emit('submit', { name: 'Tom', age: 18 })
emit('cancel')

好处:事件名拼错、参数错类型会立刻报错,不用等联调。


5. ref / reactive / computed 怎么选

5.1 ref

单值优先。

ts 复制代码
const count = ref(0)
count.value++

5.2 reactive

对象聚合状态。

ts 复制代码
const form = reactive({
    name: '',
    age: 0
})

5.3 computed

派生值,不要手动同步。

ts 复制代码
const canSubmit = computed(() => form.name.trim().length > 0 && form.age > 0)

口诀:源数据用 ref/reactive,计算结果用 computed


6. watchwatchEffect 一眼分清

6.1 watch:监听谁,你说了算

ts 复制代码
watch(
    () => form.name,
    (newName, oldName) => {
        console.log(newName, oldName)
    }
)

6.2 watchEffect:函数里用到谁,就监听谁

ts 复制代码
watchEffect(() => {
    console.log(form.name, form.age)
})

新手建议先用 watch,更可控。


7. v-model 组件写法(必会)

子组件:

vue 复制代码
<script setup lang="ts">
const props = defineProps<{
  modelValue: string
}>()

const emit = defineEmits<{
  'update:modelValue': [value: string]
}>()
</script>

<template>
  <input
      :value="props.modelValue"
      @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
  />
</template>

父组件:

vue 复制代码
<MyInput v-model="keyword" />

8. 插槽(slot)先会用,再谈高级

子组件:

vue 复制代码
<template>
  <section>
    <header><slot name="header" /></header>
    <main><slot /></main>
    <footer><slot name="footer" /></footer>
  </section>
</template>

父组件:

vue 复制代码
<BaseCard>
<template #header>标题</template>
正文内容
<template #footer>底部按钮</template>
</BaseCard>

先掌握命名插槽,已经覆盖大多数业务场景。


9. 模板 ref 获取 DOM(高频)

vue 复制代码
<script setup lang="ts">
import { onMounted, ref } from 'vue'

const inputRef = ref<HTMLInputElement | null>(null)

onMounted(() => {
  inputRef.value?.focus()
})
</script>

<template>
  <input ref="inputRef" />
</template>

关键点:模板 ref 一般要在 onMounted 后访问。


10. 父调子:defineExpose

子组件:

ts 复制代码
function reset() {
    // ...
}

defineExpose({ reset })

父组件:

ts 复制代码
const childRef = ref<{ reset: () => void } | null>(null)
childRef.value?.reset()

只暴露必要 API,别把整个内部状态暴露出去。


11. 一个完整小案例:可复用分页组件

vue 复制代码
<script setup lang="ts">
import { computed } from 'vue'

interface Props {
  total: number
  page: number
  pageSize?: number
}

const props = withDefaults(defineProps<Props>(), {
  pageSize: 10
})

const emit = defineEmits<{
  'update:page': [page: number]
}>()

const totalPages = computed(() =>
    Math.max(1, Math.ceil(props.total / props.pageSize))
)

function prev() {
  if (props.page > 1) emit('update:page', props.page - 1)
}

function next() {
  if (props.page < totalPages.value) emit('update:page', props.page + 1)
}
</script>

<template>
  <div>
    <button :disabled="page <= 1" @click="prev">上一页</button>
    <span>{{ page }} / {{ totalPages }}</span>
    <button :disabled="page >= totalPages" @click="next">下一页</button>
  </div>
</template>

这个组件同时练到了:

  • props + 默认值
  • emit 类型
  • computed
  • 边界判断

12. 新手高频报错对照

12.1 Property xxx does not exist on type

通常是变量没声明、拼写错、或推断不到类型。先看定义,再看名字。

12.2 Object is possibly 'null'

模板 ref 或接口可空字段未判空。加 ?. 或先 if 收窄。

12.3 Cannot find module '*.vue'

检查 env.d.tstsconfig include

12.4 Type 'string | undefined' is not assignable to type 'string'

给默认值:x ?? '',或者目标类型改联合类型。


13. 组件开发速查表(收藏)

需求 首选写法
接收参数 defineProps<T>()
参数默认值 withDefaults(defineProps<T>(), defaults)
抛出事件 defineEmits<{ event: [payload] }>()
双向绑定 modelValue + update:modelValue
单值状态 ref<T>()
对象状态 reactive()
派生状态 computed()
监听变化 watch()
自动收集依赖 watchEffect()
暴露子组件方法 defineExpose()
拿 DOM 模板 ref + onMounted

14. 学习顺序建议(7 天版本)

  • Day1: script setup + props/emits
  • Day2: ref/reactive/computed
  • Day3: watch + 生命周期
  • Day4: v-model 组件
  • Day5: 插槽 + defineExpose
  • Day6: 做一个表单组件
  • Day7: 做一个列表 + 分页 + 搜索组件

每天 1 小时,边写边改,进步最快。


15. 结语

Vue3 + TypeScript 难的不是 API 多,而是「组件边界和类型边界」要清晰。

只要你把 propsemitsv-model 这三件事写标准,80% 的组件问题都会少很多。

如果你愿意,我可以下一篇直接给你写一套「后台管理系统常用 10 个组件模板」(弹窗、表格、表单、上传、抽屉、树选择等),复制即用。

---)

相关推荐
AnalogElectronic2 小时前
uniapp学习6,滚动字幕播报
javascript·学习·uni-app
全马必破三2 小时前
Vue3+Node.js 实现AI流式输出全解析
前端·javascript·node.js
belldeep2 小时前
前端:TypeScript 版本 2 , 3 , 4 , 5 , 6 有什么差别?
前端·javascript·typescript
狗都不学爬虫_2 小时前
JS逆向 - Akamai阿迪达斯(三次) 补环境、纯算
javascript·爬虫·python·网络爬虫·wasm
液态不合群2 小时前
Redis命令处理机制源码探究
前端·redis·bootstrap
指尖的记忆3 小时前
前端 Monorepo 实战指南:仓库多到切疯?
前端
csdn2015_3 小时前
java 把对象转化为json字符串
java·前端·json
shughui3 小时前
Fiddler(二):自动转发(AutoResponder)功能详解
前端·测试工具·fiddler
初见雨夜3 小时前
OpenAI 官方出手:把 Codex 接进 Claude Code
前端·openai·ai编程