版权声明
- 本文原创作者:谷哥的小弟
- 作者博客地址:http://blog.csdn.net/lfdfhl

TypeScript在Vue3中的常见写法概述
学习 Vue3 时,经常会看到 TypeScript 语法和 Vue3 代码写在一起。对初学者来说,这类代码容易显得陌生,并不是因为其中每一部分都是新知识,而是因为 TypeScript 类型写法和 Vue3 API 同时出现。比如 const username = ref<string>("jack") 这一行中,ref 属于 Vue3 内容,而 <string> 属于 TypeScript 泛型写法。提前识别这些 TypeScript 部分,可以减少后续学习 Vue3 时的理解压力。
Vue3 中常见的 ref<类型> 写法,本质上是 TypeScript 泛型的使用。ref<string> 表示其中保存的是字符串,ref<number> 表示其中保存的是数字,ref<boolean> 表示其中保存的是布尔值。当前阶段不需要深入理解 ref 的工作原理,只要先看懂尖括号中的类型含义即可。它和前面学习的 Array<string>、Promise<number> 一样,都是通过尖括号传入类型信息。
当 ref 或 reactive 中保存的是对象时,通常会先使用类型别名或接口描述对象结构。例如,先定义 User 类型,说明用户对象包含 username 和 age,再写成 ref<User> 或 reactive<User>。这种写法表示其中保存的数据应该符合 User 所描述的对象结构。对象结构较复杂时,先用 type 或 interface 定义类型,再放到泛型中使用,代码会更加清晰。
列表数据在 Vue3 中非常常见,例如商品列表、用户列表、文章列表等。列表通常使用数组表示,如果数组中的每一项是对象,就可以使用对象数组类型。例如,Product[] 表示商品对象数组,ref<Product[]> 表示 ref 中保存的是商品数组。看到这类写法时,可以把它和前面学过的 string[]、number[] 联系起来理解,它们本质上都是数组类型,只是数组元素从基础类型变成了对象类型。
请求状态、页面状态、显示模式等有限范围的数据,常使用字面量联合类型表示。例如,type RequestStatus = "loading" | "success" | "error" 表示请求状态只能从这三个固定值中选择。再配合 ref<RequestStatus>("loading"),就可以限制状态值只能是允许范围内的字符串。如果把状态赋值为 "finished",TypeScript 就会提示它不属于允许的状态值。
Vue3 项目中经常处理接口返回数据,而接口返回的数据并不一定每个字段都完整。例如,用户对象可能一定有 id 和 username,但头像地址 avatar 不一定存在。这时可以使用可选属性,写成 avatar?: string。可选属性表示这个字段可以没有;如果有,类型必须正确。使用可选属性前,要考虑它可能是 undefined,必要时先进行判断。
Vue3 中还会经常编写点击、输入、提交等事件处理函数。这些函数的参数和返回值也可以添加类型标注。例如,id: number 表示参数必须是数字,: void 表示函数没有返回值。如果函数接收一个对象参数,可以使用类型别名或接口描述参数对象结构,例如 product: Product 表示参数必须是商品对象。这样可以让函数接收的数据更明确。
在表单输入事件中,类型断言也很常见。事件对象中的 event.target 类型比较宽泛,TypeScript 不知道它一定是输入框。如果要访问输入框的 value,就可以使用 event.target as HTMLInputElement,表示把事件目标当作输入框元素来看待。在 Vue3 模板中,类似 ($event.target as HTMLInputElement).value 的写法,也是同样的类型断言思路。
组件相关代码中经常会看到 defineProps。当前阶段不需要深入理解组件功能,只要能识别其中的 TypeScript 类型写法即可。例如,defineProps<{ title: string; count?: number }>() 中,defineProps 属于 Vue3,尖括号中的对象结构属于 TypeScript 类型写法。title: string 表示必需属性,count?: number 表示可选属性。如果属性较多,也可以先定义 Props 接口,再写成 defineProps<Props>()。
后续结合接口请求时,还会看到 Promise<User>、Promise<User[]> 这类写法。Promise<User> 表示异步任务成功后得到一个用户对象,Promise<User[]> 表示异步任务成功后得到用户对象数组。这类写法仍然是泛型,只是用于描述异步结果的数据类型。理解尖括号中的类型含义后,就能更容易看懂接口请求函数的返回值。
如果外部数据结构暂时不明确,可以先使用 unknown 表示未知数据,但使用前要进行类型判断,不能像 any 一样随意使用。数据结构明确时,应优先使用类型别名或接口;只知道有限几种取值时,可以使用联合类型或字面量联合类型;确实暂时无法确定数据结构时,再考虑使用 unknown。这些写法都来自前面已经学过的 TypeScript 基础内容,后续正式学习 Vue3 时,再进一步理解 ref、reactive、模板语法和组件语法本身。
案例源码
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>TypeScript 在 Vue3 中的常见写法预备</title>
</head>
<body>
<h1>TypeScript 在 Vue3 中的常见写法预备</h1>
<p>
本节用于整理 Vue3 中经常出现的 TypeScript 写法。
这些写法不是新的 TypeScript 语法,而是前面已经学过的类型标注、对象类型、接口、类型别名、联合类型、字面量类型、类型断言和泛型等内容的组合使用。
</p>
<p>
本节不正式讲解 Vue3 的具体功能,也不要求现在运行这些 Vue3 代码。
当前目标是:看到这些写法时,能够大致判断它们属于哪类 TypeScript 语法,减少后续学习 Vue3 时的陌生感。
</p>
<p>
说明:本节中部分代码是 Vue3 中的常见写法片段,主要用于识别 TypeScript 语法。
如果是普通 TypeScript 代码,可以继续使用
<a href="https://www.typescriptlang.org/play/" target="_blank">TypeScript Playground</a>
测试;如果代码中出现 <code>ref</code>、<code>reactive</code>、<code>defineProps</code> 等 Vue3 写法,后续进入 Vue3 项目后再运行。
</p>
<hr>
<h2>一、为什么需要提前认识这些写法</h2>
<p>
学习 Vue3 时,经常会看到 TypeScript 和 Vue3 代码混在一起。
例如:
</p>
<pre><code>const username = ref<string>("jack");</code></pre>
<p>
对新手来说,这行代码可能同时出现几个疑问:
</p>
<ol>
<li><code>ref</code> 是什么?</li>
<li><code><string></code> 是什么?</li>
<li><code>"jack"</code> 为什么放在括号里?</li>
</ol>
<p>
其中,<code>ref</code> 属于 Vue3 的内容,后面正式学习 Vue3 时再讲。
但是 <code><string></code> 属于 TypeScript 泛型写法,前面已经学习过。
</p>
<p>
所以,本节的重点不是讲 Vue3 功能,而是提前识别 Vue3 代码中常见的 TypeScript 部分。
</p>
<hr>
<h2>二、ref 中的泛型写法</h2>
<p>
Vue3 中经常会看到 <code>ref<类型></code> 这种写法。
</p>
<pre><code>const username = ref<string>("jack");
const age = ref<number>(18);
const isLogin = ref<boolean>(false);</code></pre>
<p>
这里先不用理解 <code>ref</code> 的具体作用。
只看 TypeScript 部分:
</p>
<ol>
<li><code>ref<string></code> 表示里面保存的是字符串。</li>
<li><code>ref<number></code> 表示里面保存的是数字。</li>
<li><code>ref<boolean></code> 表示里面保存的是布尔值。</li>
</ol>
<p>
这和前面学习的泛型写法类似。
</p>
<pre><code>Array<string>
Promise<number>
ref<boolean></code></pre>
<p>
它们共同点是:
<strong>尖括号中的内容用于说明内部数据的类型。</strong>
</p>
<hr>
<h2>三、ref 中的对象类型</h2>
<p>
如果 <code>ref</code> 中保存的是对象,可以使用类型别名或接口描述对象结构。
</p>
<pre><code>type User = {
username: string;
age: number;
};
const user = ref<User>({
username: "jack",
age: 18
});</code></pre>
<p>
这段代码中:
</p>
<ol>
<li><code>User</code> 是类型别名。</li>
<li><code>User</code> 描述用户对象应该有 <code>username</code> 和 <code>age</code>。</li>
<li><code>ref<User></code> 表示 <code>ref</code> 中保存的是一个用户对象。</li>
</ol>
<p>
如果使用接口,也可以这样写:
</p>
<pre><code>interface Product {
name: string;
price: number;
}
const product = ref<Product>({
name: "键盘",
price: 199
});</code></pre>
<p>
对初学者来说,可以先记住:
<strong>对象结构较复杂时,先用 type 或 interface 定义结构,再放到泛型中使用。</strong>
</p>
<hr>
<h2>四、数组数据中的类型写法</h2>
<p>
Vue3 中经常会处理列表数据。
列表通常是数组,如果数组中的每一项是对象,就可以使用对象数组类型。
</p>
<pre><code>type Product = {
id: number;
name: string;
price: number;
};
const productList = ref<Product[]>([
{ id: 101, name: "键盘", price: 199 },
{ id: 102, name: "鼠标", price: 99 }
]);</code></pre>
<p>
这段代码中:
</p>
<ol>
<li><code>Product</code> 表示单个商品对象类型。</li>
<li><code>Product[]</code> 表示商品对象数组。</li>
<li><code>ref<Product[]></code> 表示 <code>ref</code> 中保存的是商品数组。</li>
</ol>
<p>
看到 <code>Product[]</code> 时,不要把它理解复杂。
它就是前面学过的数组类型:
</p>
<pre><code>string[] // 字符串数组
number[] // 数字数组
Product[] // 商品对象数组</code></pre>
<hr>
<h2>五、reactive 中的对象类型</h2>
<p>
Vue3 中也会看到 <code>reactive</code> 这种写法。
现在不需要理解它的具体作用,只看 TypeScript 如何描述对象结构。
</p>
<pre><code>type User = {
username: string;
age: number;
isLogin: boolean;
};
const user = reactive<User>({
username: "jack",
age: 18,
isLogin: false
});</code></pre>
<p>
这段代码中:
</p>
<ol>
<li><code>User</code> 描述对象结构。</li>
<li><code>reactive<User></code> 表示这个对象应该符合 <code>User</code> 类型。</li>
<li>对象中的 <code>username</code>、<code>age</code>、<code>isLogin</code> 都要符合类型要求。</li>
</ol>
<p>
后续正式学习 Vue3 时,再详细学习 <code>ref</code> 和 <code>reactive</code> 的区别。
当前只要能看懂尖括号中的类型即可。
</p>
<hr>
<h2>六、请求状态中的字面量类型</h2>
<p>
前端页面经常需要表示请求状态。
例如:加载中、成功、失败。
</p>
<pre><code>type RequestStatus = "loading" | "success" | "error";
const status = ref<RequestStatus>("loading");</code></pre>
<p>
这里使用了前面学过的字面量联合类型。
</p>
<ol>
<li><code>"loading"</code> 表示固定字符串。</li>
<li><code>"success"</code> 表示固定字符串。</li>
<li><code>"error"</code> 表示固定字符串。</li>
<li><code>RequestStatus</code> 只能从这三个值中选择一个。</li>
</ol>
<p>
下面这种赋值是正确的:
</p>
<pre><code>status.value = "success";
status.value = "error";</code></pre>
<p>
下面这种赋值是错误的:
</p>
<pre><code>status.value = "finished";</code></pre>
<p>
错误原因:
<code>"finished"</code> 不属于 <code>RequestStatus</code> 允许的状态值。
</p>
<hr>
<h2>七、对象中的可选属性</h2>
<p>
Vue3 项目中经常会处理接口返回数据。
有些字段不是每次都有,这时可以使用可选属性。
</p>
<pre><code>type User = {
id: number;
username: string;
avatar?: string;
};</code></pre>
<p>
这段代码中:
</p>
<ol>
<li><code>id</code> 是必需属性。</li>
<li><code>username</code> 是必需属性。</li>
<li><code>avatar?</code> 是可选属性。</li>
</ol>
<p>
所以下面两种用户对象都符合 <code>User</code> 类型。
</p>
<pre><code>const user1: User = {
id: 101,
username: "jack"
};
const user2: User = {
id: 102,
username: "lucy",
avatar: "lucy.png"
};</code></pre>
<p>
使用可选属性时,要注意它可能是 <code>undefined</code>。
</p>
<pre><code>if (user1.avatar !== undefined) {
console.log(user1.avatar);
}</code></pre>
<hr>
<h2>八、函数参数中的类型标注</h2>
<p>
Vue3 中经常会写一些函数,用来处理点击、输入、提交等操作。
这些函数的参数也可以添加类型标注。
</p>
<pre><code>function selectProduct(id: number): void {
console.log("选中的商品编号:" + id);
}</code></pre>
<p>
这段代码中:
</p>
<ol>
<li><code>id: number</code> 表示参数必须是数字。</li>
<li><code>: void</code> 表示函数没有返回值。</li>
</ol>
<p>
如果函数接收对象参数,可以使用类型别名。
</p>
<pre><code>type Product = {
id: number;
name: string;
price: number;
};
function addToCart(product: Product): void {
console.log("加入购物车:" + product.name);
}</code></pre>
<p>
这段代码中,<code>product: Product</code> 表示参数必须是商品对象。
</p>
<hr>
<h2>九、事件对象中的类型断言</h2>
<p>
后续学习 Vue3 表单输入时,经常会看到类似写法:
</p>
<pre><code>function handleInput(event: Event): void {
const input = event.target as HTMLInputElement;
console.log(input.value);
}</code></pre>
<p>
这里出现了类型断言:
</p>
<pre><code>event.target as HTMLInputElement</code></pre>
<p>
它表示:
把事件目标当作输入框元素来看待。
</p>
<p>
这样写的原因是:
TypeScript 只知道 <code>event.target</code> 是事件目标,
但不知道它一定是输入框。
如果要访问输入框的 <code>value</code>,就需要告诉 TypeScript:
这个目标元素是 <code>HTMLInputElement</code>。
</p>
<p>
在 Vue3 模板中,也可能看到类似写法:
</p>
<pre><code>@input="username = ($event.target as HTMLInputElement).value"</code></pre>
<p>
当前阶段不需要深入理解模板语法。
只要知道其中的 <code>as HTMLInputElement</code> 属于 TypeScript 类型断言即可。
</p>
<hr>
<h2>十、defineProps 中的对象类型</h2>
<p>
Vue3 组件中经常会看到 <code>defineProps</code>。
当前不需要深入学习组件,只先识别其中的 TypeScript 写法。
</p>
<pre><code>const props = defineProps<{
title: string;
count?: number;
}>();</code></pre>
<p>
这段代码中:
</p>
<ol>
<li><code>defineProps</code> 属于 Vue3。</li>
<li><code><{ title: string; count?: number }></code> 是 TypeScript 类型写法。</li>
<li><code>title: string</code> 表示 <code>title</code> 是必需属性。</li>
<li><code>count?: number</code> 表示 <code>count</code> 是可选属性。</li>
</ol>
<p>
如果对象结构较多,也可以先定义接口。
</p>
<pre><code>interface Props {
title: string;
count?: number;
}
const props = defineProps<Props>();</code></pre>
<p>
这里的 <code>Props</code> 就是接口,用来描述组件接收的数据结构。
</p>
<hr>
<h2>十一、Promise 中的泛型写法</h2>
<p>
后续结合接口请求时,经常会看到 <code>Promise<类型></code>。
</p>
<pre><code>type User = {
id: number;
username: string;
};
function getUser(): Promise<User> {
return Promise.resolve({
id: 101,
username: "jack"
});
}</code></pre>
<p>
这段代码中:
</p>
<ol>
<li><code>Promise<User></code> 表示函数返回一个 Promise。</li>
<li>这个 Promise 成功后的结果是 <code>User</code> 类型。</li>
<li><code>User</code> 描述用户对象结构。</li>
</ol>
<p>
如果返回的是用户数组,可以写成:
</p>
<pre><code>function getUserList(): Promise<User[]> {
return Promise.resolve([
{ id: 101, username: "jack" },
{ id: 102, username: "lucy" }
]);
}</code></pre>
<p>
<code>Promise<User[]></code> 表示:
异步任务成功后得到的是用户对象数组。
</p>
<hr>
<h2>十二、unknown 和接口数据</h2>
<p>
有时外部数据的结构暂时不明确,可以先使用 <code>unknown</code>。
但是使用前要判断类型,不能像 <code>any</code> 一样随意使用。
</p>
<pre><code>let responseData: unknown = {
id: 101,
username: "jack"
};
if (typeof responseData === "object" && responseData !== null) {
console.log("接口返回的是对象");
}</code></pre>
<p>
当前阶段可以先记住:
</p>
<ol>
<li>数据结构明确时,优先使用类型别名或接口。</li>
<li>数据结构暂时未知时,可以使用 <code>unknown</code>。</li>
<li>不建议为了省事直接使用 <code>any</code>。</li>
</ol>
<hr>
<h2>十三、常见错误写法</h2>
<p>
下面列出一些进入 Vue3 学习前容易混淆的地方。
</p>
<h3>错误一:把 ref<string> 当成 Vue3 独有语法</h3>
<pre><code>const username = ref<string>("jack");</code></pre>
<p>
正确理解:
<code>ref</code> 属于 Vue3,<code><string></code> 属于 TypeScript 泛型写法。
</p>
<h3>错误二:把 Product[] 看成复杂语法</h3>
<pre><code>const productList = ref<Product[]>([]);</code></pre>
<p>
正确理解:
<code>Product[]</code> 表示商品对象数组。
它和 <code>string[]</code>、<code>number[]</code> 的结构是一样的。
</p>
<h3>错误三:忘记可选属性可能是 undefined</h3>
<pre><code>type User = {
username: string;
avatar?: string;
};
function printAvatar(user: User): void {
console.log(user.avatar.length);
}</code></pre>
<p>
错误原因:
<code>avatar</code> 是可选属性,可能是 <code>undefined</code>。
</p>
<p>
更安全的写法:
</p>
<pre><code>function printAvatar(user: User): void {
if (user.avatar !== undefined) {
console.log(user.avatar.length);
}
}</code></pre>
<h3>错误四:把类型断言当成类型转换</h3>
<pre><code>const input = event.target as HTMLInputElement;</code></pre>
<p>
正确理解:
<code>as HTMLInputElement</code> 只是类型断言,
表示把事件目标当作输入框元素使用。
它不会改变运行时的数据本身。
</p>
<hr>
<h2>十四、完整小示例</h2>
<p>
下面通过一个商品列表和请求状态的示例,综合演示 Vue3 中常见的 TypeScript 写法。
这段代码主要用于识别类型写法,后续进入 Vue3 项目后再运行。
</p>
<pre><code>// 商品类型
type Product = {
id: number;
name: string;
price: number;
description?: string;
};
// 请求状态
type RequestStatus = "loading" | "success" | "error";
// 商品列表:Product[] 表示商品对象数组
const productList = ref<Product[]>([
{ id: 101, name: "键盘", price: 199 },
{ id: 102, name: "鼠标", price: 99, description: "无线鼠标" }
]);
// 当前请求状态
const status = ref<RequestStatus>("loading");
// 选中商品编号
const selectedProductId = ref<number>(101);
// 打印商品信息
function printProduct(product: Product): void {
console.log("商品编号:" + product.id);
console.log("商品名称:" + product.name);
console.log("商品价格:" + product.price);
if (product.description !== undefined) {
console.log("商品描述:" + product.description);
}
}
// 根据状态输出提示
function printStatus(status: RequestStatus): void {
if (status === "loading") {
console.log("正在加载商品数据");
}
if (status === "success") {
console.log("商品数据加载成功");
}
if (status === "error") {
console.log("商品数据加载失败");
}
}
// 处理输入事件
function handleInput(event: Event): void {
const input = event.target as HTMLInputElement;
console.log("输入内容:" + input.value);
}
// 遍历商品
productList.value.forEach(function (item) {
printProduct(item);
});
// 修改状态
status.value = "success";
printStatus(status.value);
// 错误示例
// status.value = "finished";
// selectedProductId.value = "101";
// productList.value.push({ id: 103, name: "显示器", price: "899" });</code></pre>
<p>
这个示例中:
</p>
<ol>
<li><code>Product</code> 使用类型别名描述商品对象结构。</li>
<li><code>description?</code> 表示商品描述是可选属性。</li>
<li><code>RequestStatus</code> 使用字面量联合类型限制请求状态。</li>
<li><code>ref<Product[]></code> 表示保存商品对象数组。</li>
<li><code>ref<RequestStatus></code> 表示保存请求状态。</li>
<li><code>product: Product</code> 表示函数参数必须是商品对象。</li>
<li><code>event.target as HTMLInputElement</code> 是类型断言。</li>
</ol>
<p>
这些写法都来自前面已经学过的 TypeScript 基础内容。
后续正式学习 Vue3 时,只需要再理解 <code>ref</code>、<code>reactive</code>、模板语法和组件语法本身即可。
</p>
<hr>
<h2>十五、本节总结</h2>
<p>
本节整理了 Vue3 中常见的 TypeScript 写法。
</p>
<ol>
<li><code>ref<string></code> 中的 <code><string></code> 是 TypeScript 泛型写法。</li>
<li><code>ref<number></code> 表示保存数字。</li>
<li><code>ref<boolean></code> 表示保存布尔值。</li>
<li><code>ref<Product[]></code> 表示保存商品对象数组。</li>
<li>对象结构可以使用 <code>type</code> 或 <code>interface</code> 描述。</li>
<li>状态值可以使用字面量联合类型限制取值范围。</li>
<li>可选属性使用 <code>?</code>,使用前要考虑它可能是 <code>undefined</code>。</li>
<li>函数参数和返回值可以添加类型标注。</li>
<li><code>event.target as HTMLInputElement</code> 是常见类型断言。</li>
<li><code>Promise<User></code> 表示异步任务成功后得到用户对象。</li>
<li>数据结构明确时,优先使用明确类型,不建议随意使用 <code>any</code>。</li>
<li>数据结构未知时,可以使用 <code>unknown</code>,但使用前要先判断类型。</li>
</ol>
</body>
</html>