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 个组件模板」(弹窗、表格、表单、上传、抽屉、树选择等),复制即用。

---)

相关推荐
To_OC5 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC6 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble8 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长8 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能