Vue3 零基础每日笔记(043):defineModel 与 defineExpose 的 TS 用法——3.4+ 新 API 进阶

一、前言

027 篇学过 defineModel 的基础用法,033 篇用过 defineExpose 开门------本篇给这两个"新 API"穿上 TS 盔甲:模型 ref 的类型怎么标?父组件拿到的子组件实例怎么声明类型?033 篇"InstanceType 伏笔"今天兑现。

二、defineModel 的类型标注

基本形态:泛型里直接写值的类型:

vue 复制代码
<!-- src/components/TsInput.vue -->
<template>
  <input :value="model" @input="model = ($event.target as HTMLInputElement).value" />
</template>

<script setup lang="ts">
// Ref<string | undefined>:父组件可以不传
const model = defineModel<string>()

// 必填模型:父组件必须 v-model
const requiredModel = defineModel<string>({ required: true })

// 带默认值:类型自动收窄为 string
const withDefault = defineModel<string>({ default: '' })
</script>

as HTMLInputElement 是本篇第一个类型断言:$event.target 默认类型是 EventTarget(没有 .value),"我确定它是 input 元素" 就用 as 断言------比事件处理器里手写类型更省事。模板里更常见的写法其实还是 v-model="model",一行都不用断言:

vue 复制代码
<template>
  <input v-model="model" />
</template>

具名模型 + 默认值 + 校验全套:

ts 复制代码
// v-model:title 场景
const title = defineModel<string>('title', {
  default: '未命名',
  validator: (v: string) => v.length <= 20
})

// v-model:id 场景(数字)
const id = defineModel<number>('id')

父组件侧无需任何额外标注------v-model 绑定的 ref 类型对上即可:

vue 复制代码
<template>
  <!-- titleRef 是 Ref<string>,和子组件 model 类型匹配 -->
  <TsInput v-model="titleRef" />
  <TsInput v-model:id="idRef" />
</template>

类型对不上(比如子组件要 number 你绑了 string)编辑器直接红线------组件的 v-model 协议也被编译器看守了

三、defineExpose 与 InstanceType:给实例开门挂牌

033 篇的 JS 版本里,父组件 const panelRef = ref(null) 一把梭。TS 版本要回答两个问题:子组件暴露了什么类型?父组件的 ref 该标什么?

先看子组件(defineExpose 本身不需要泛型------传入对象即推断):

vue 复制代码
<!-- src/components/CountPanel.vue -->
<template>
  <div>
    <p>内部计数:{{ count }}</p>
    <button @click="count++">内部 +1</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)

function reset(): void {
  count.value = 0
}

// 传入对象即推断,无需手写泛型;想强约束也可以 defineExpose<{...}>({...})
defineExpose({ count, reset })
</script>

父组件用 InstanceType<typeof 组件> 取得实例类型:

vue 复制代码
<template>
  <CountPanel ref="panelRef" />
  <button @click="remote">父组件遥控</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import CountPanel from './components/CountPanel.vue'

// ★ 核心句式:组件类型 → 实例类型
const panelRef = ref<InstanceType<typeof CountPanel> | null>(null)

function remote() {
  if (!panelRef.value) return
  panelRef.value.reset()                 // ✅ 有提示:reset(): void
  const c: number = panelRef.value.count // ✅ count 自动解包成 number
  console.log(c)
}
</script>

拆解这个句式:

  • typeof CountPanel:拿到组件定义的类型(typeof 在类型层面取"变量"的类型)
  • InstanceType<...>:工具类型,取"这个组件实例化后"的类型------自动包含 defineExpose 的所有成员
  • | null:挂载前是 null,手动加上(或用可选链 panelRef.value?.reset()

defineExpose 没暴露的成员,类型上同样拿不到------运行时的"门"和类型上的"门"是同一扇,编译器替 033 篇的规矩执法。

【截图位置:panelRef.value. 后自动提示 count/reset 的 IDE 截图】

四、DOM ref 的类型标注

拿 DOM 元素(033 篇 input 聚焦案例)的 TS 版:

vue 复制代码
<template>
  <input ref="inputRef" placeholder="自动聚焦" />
</template>

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

// DOM 类型:HTMLInputElement / HTMLDivElement / HTMLCanvasElement...
const inputRef = ref<HTMLInputElement | null>(null)

onMounted(() => {
  inputRef.value?.focus()   // 可选链,挂载前调用也不炸
})
</script>

DOM 类型速查:input→HTMLInputElement、div→HTMLDivElement、img→HTMLImageElement、video→HTMLVideoElement、通用容器兜底 HTMLElement。命名规律:HTML + 标签名首字母大写 + Element。

Vue 3.5+ 的 useTemplateRef 可以少写一遍变量名对应:

ts 复制代码
import { useTemplateRef } from 'vue'

// 泛型直接标 DOM 类型,字符串对应模板里的 ref 名
const inputRef = useTemplateRef<HTMLInputElement>('inputRef')

两种写法都对,项目统一即可。

五、综合实战:类型化的表单遥控器

033 篇 EmailForm 的完整 TS 化(表单 + validate/reset + 父组件遥控):

vue 复制代码
<!-- src/components/EmailForm.vue -->
<template>
  <div>
    <input v-model.trim="email" placeholder="邮箱" />
    <p v-if="error" class="err">{{ error }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const email = ref('')
const error = ref('')

function validate(): boolean {
  const ok = /^[\w.-]+@[\w-]+(\.[\w-]+)+$/.test(email.value)
  error.value = ok ? '' : '邮箱格式不正确'
  return ok
}

function reset(): void {
  email.value = ''
  error.value = ''
}

defineExpose({ validate, reset })
</script>

<style scoped>
.err { color: #e74c3c; font-size: 12px; }
</style>
vue 复制代码
<!-- App.vue -->
<template>
  <EmailForm ref="formRef" />
  <button @click="onSubmit">提交</button>
  <button @click="formRef?.reset()">重置</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import EmailForm from './components/EmailForm.vue'

const formRef = ref<InstanceType<typeof EmailForm> | null>(null)

function onSubmit() {
  // validate 的返回值类型 boolean,写错判断逻辑编译器能看出来
  if (formRef.value?.validate()) {
    console.log('校验通过,提交')
  }
}
</script>

对比 033 篇 JS 版:功能一致,但 formRef.value.validateXxx()(方法名拼错)、validate() 返回值当数字用,全部变成编译期错误。

【截图位置:表单校验失败提示 + 提交成功日志的运行效果】

六、踩坑记录

  1. InstanceType 拿不到成员:子组件没 defineExpose 或没暴露该方法------类型层面同样"关门",先检查子组件开了哪些门
  2. ref 忘标 | nullref<InstanceType<typeof Child>>(null) 报错(null 不在类型里)------模板挂载前是 null,类型必须含 null
  3. 模板里 $event.target as 写错:断言目标写 HTMLElement 没有 .value------input 用 HTMLInputElement,select 用 HTMLSelectElement
  4. defineModel 泛型标了基本类型却绑对象defineModel<string>()v-model="userObj" 红线------模型类型必须与父组件数据一致
  5. useTemplateRef 找不到(报没有这个导出) :它是 3.5+ API,老版本用 ref<HTMLInputElement | null>(null) 经典写法
  6. as 滥用:实在拿不准才断言;到处 as any 等于卸甲------断言是"签名担保"不是"闭嘴器"

七、今日小结

  • defineModel<T>():泛型标值类型,required/default/validator 全支持,具名模型第二参数传名字
  • defineExpose 传对象即推断;父组件用 ref<InstanceType<typeof 子组件> | null>(null) 拿实例类型
  • DOM ref 标 HTMLXxxElement;3.5+ 可用 useTemplateRef<T>('名字')
  • as 断言用在没有类型的边界($event.target),边界内全靠接口和推断

下篇预告

TS 篇压轴:泛型组件------同一个列表组件,给用户列表用 item 是 User,给商品列表用 item 是 Product,类型还全程精准。下一篇 044 泛型组件(generic attribute)实战。

相关推荐
洛兮银儿1 小时前
前端页面引入cesium
前端·cesium
SEO_juper1 小时前
2026年用Python分析网站访问日志:看清Googlebot和AI爬虫怎么爬你的站(附完整代码)
开发语言·前端·seo·独立站·谷歌优化
摸鱼仙人~1 小时前
React 原理进阶:彻底理解 re-render、React.memo、useMemo 与 useCallback
前端·vue.js
YHHLAI1 小时前
从 SPA 到 Next.js 全栈:一个大前端的 SEO 突围笔记
javascript
IMPYLH1 小时前
HTML 的 <small> 元素
前端·网络·html
IT_陈寒1 小时前
Vue的响应式让我加班到凌晨,问题竟出在这个不起眼的地方
前端·人工智能·后端
avi91111 小时前
无厘头 -- 基于vue 公用状态机,已用于放置类篮球卡牌Game
中间件·vue·公用状态·文件间通信
AlienZHOU10 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc13 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding