TypeScript在Vue3中的常见写法


版权声明


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> 一样,都是通过尖括号传入类型信息。

refreactive 中保存的是对象时,通常会先使用类型别名或接口描述对象结构。例如,先定义 User 类型,说明用户对象包含 usernameage,再写成 ref<User>reactive<User>。这种写法表示其中保存的数据应该符合 User 所描述的对象结构。对象结构较复杂时,先用 typeinterface 定义类型,再放到泛型中使用,代码会更加清晰。

列表数据在 Vue3 中非常常见,例如商品列表、用户列表、文章列表等。列表通常使用数组表示,如果数组中的每一项是对象,就可以使用对象数组类型。例如,Product[] 表示商品对象数组,ref<Product[]> 表示 ref 中保存的是商品数组。看到这类写法时,可以把它和前面学过的 string[]number[] 联系起来理解,它们本质上都是数组类型,只是数组元素从基础类型变成了对象类型。

请求状态、页面状态、显示模式等有限范围的数据,常使用字面量联合类型表示。例如,type RequestStatus = "loading" | "success" | "error" 表示请求状态只能从这三个固定值中选择。再配合 ref<RequestStatus>("loading"),就可以限制状态值只能是允许范围内的字符串。如果把状态赋值为 "finished",TypeScript 就会提示它不属于允许的状态值。

Vue3 项目中经常处理接口返回数据,而接口返回的数据并不一定每个字段都完整。例如,用户对象可能一定有 idusername,但头像地址 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 时,再进一步理解 refreactive、模板语法和组件语法本身。

案例源码

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&lt;string&gt;("jack");</code></pre>

  <p>
    对新手来说,这行代码可能同时出现几个疑问:
  </p>

  <ol>
    <li><code>ref</code> 是什么?</li>
    <li><code>&lt;string&gt;</code> 是什么?</li>
    <li><code>"jack"</code> 为什么放在括号里?</li>
  </ol>

  <p>
    其中,<code>ref</code> 属于 Vue3 的内容,后面正式学习 Vue3 时再讲。
    但是 <code>&lt;string&gt;</code> 属于 TypeScript 泛型写法,前面已经学习过。
  </p>

  <p>
    所以,本节的重点不是讲 Vue3 功能,而是提前识别 Vue3 代码中常见的 TypeScript 部分。
  </p>

  <hr>

  <h2>二、ref 中的泛型写法</h2>

  <p>
    Vue3 中经常会看到 <code>ref&lt;类型&gt;</code> 这种写法。
  </p>

  <pre><code>const username = ref&lt;string&gt;("jack");
const age = ref&lt;number&gt;(18);
const isLogin = ref&lt;boolean&gt;(false);</code></pre>

  <p>
    这里先不用理解 <code>ref</code> 的具体作用。
    只看 TypeScript 部分:
  </p>

  <ol>
    <li><code>ref&lt;string&gt;</code> 表示里面保存的是字符串。</li>
    <li><code>ref&lt;number&gt;</code> 表示里面保存的是数字。</li>
    <li><code>ref&lt;boolean&gt;</code> 表示里面保存的是布尔值。</li>
  </ol>

  <p>
    这和前面学习的泛型写法类似。
  </p>

  <pre><code>Array&lt;string&gt;
Promise&lt;number&gt;
ref&lt;boolean&gt;</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&lt;User&gt;({
  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&lt;User&gt;</code> 表示 <code>ref</code> 中保存的是一个用户对象。</li>
  </ol>

  <p>
    如果使用接口,也可以这样写:
  </p>

  <pre><code>interface Product {
  name: string;
  price: number;
}

const product = ref&lt;Product&gt;({
  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&lt;Product[]&gt;([
  { 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&lt;Product[]&gt;</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&lt;User&gt;({
  username: "jack",
  age: 18,
  isLogin: false
});</code></pre>

  <p>
    这段代码中:
  </p>

  <ol>
    <li><code>User</code> 描述对象结构。</li>
    <li><code>reactive&lt;User&gt;</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&lt;RequestStatus&gt;("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&lt;{
  title: string;
  count?: number;
}&gt;();</code></pre>

  <p>
    这段代码中:
  </p>

  <ol>
    <li><code>defineProps</code> 属于 Vue3。</li>
    <li><code>&lt;{ title: string; count?: number }&gt;</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&lt;Props&gt;();</code></pre>

  <p>
    这里的 <code>Props</code> 就是接口,用来描述组件接收的数据结构。
  </p>

  <hr>

  <h2>十一、Promise 中的泛型写法</h2>

  <p>
    后续结合接口请求时,经常会看到 <code>Promise&lt;类型&gt;</code>。
  </p>

  <pre><code>type User = {
  id: number;
  username: string;
};

function getUser(): Promise&lt;User&gt; {
  return Promise.resolve({
    id: 101,
    username: "jack"
  });
}</code></pre>

  <p>
    这段代码中:
  </p>

  <ol>
    <li><code>Promise&lt;User&gt;</code> 表示函数返回一个 Promise。</li>
    <li>这个 Promise 成功后的结果是 <code>User</code> 类型。</li>
    <li><code>User</code> 描述用户对象结构。</li>
  </ol>

  <p>
    如果返回的是用户数组,可以写成:
  </p>

  <pre><code>function getUserList(): Promise&lt;User[]&gt; {
  return Promise.resolve([
    { id: 101, username: "jack" },
    { id: 102, username: "lucy" }
  ]);
}</code></pre>

  <p>
    <code>Promise&lt;User[]&gt;</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" &amp;&amp; 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&lt;string&gt; 当成 Vue3 独有语法</h3>

  <pre><code>const username = ref&lt;string&gt;("jack");</code></pre>

  <p>
    正确理解:
    <code>ref</code> 属于 Vue3,<code>&lt;string&gt;</code> 属于 TypeScript 泛型写法。
  </p>

  <h3>错误二:把 Product[] 看成复杂语法</h3>

  <pre><code>const productList = ref&lt;Product[]&gt;([]);</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&lt;Product[]&gt;([
  { id: 101, name: "键盘", price: 199 },
  { id: 102, name: "鼠标", price: 99, description: "无线鼠标" }
]);

// 当前请求状态
const status = ref&lt;RequestStatus&gt;("loading");

// 选中商品编号
const selectedProductId = ref&lt;number&gt;(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&lt;Product[]&gt;</code> 表示保存商品对象数组。</li>
    <li><code>ref&lt;RequestStatus&gt;</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&lt;string&gt;</code> 中的 <code>&lt;string&gt;</code> 是 TypeScript 泛型写法。</li>
    <li><code>ref&lt;number&gt;</code> 表示保存数字。</li>
    <li><code>ref&lt;boolean&gt;</code> 表示保存布尔值。</li>
    <li><code>ref&lt;Product[]&gt;</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&lt;User&gt;</code> 表示异步任务成功后得到用户对象。</li>
    <li>数据结构明确时,优先使用明确类型,不建议随意使用 <code>any</code>。</li>
    <li>数据结构未知时,可以使用 <code>unknown</code>,但使用前要先判断类型。</li>
  </ol>
</body>
</html>
相关推荐
njsgcs2 小时前
链轮 平键和键槽的标准尺寸 计算 typescript
typescript·机械工程
人间凡尔赛2 小时前
2026 前端全栈新范式:Server Actions + Edge Runtime,告别传统 API 路由
前端·全栈·next.js
mONESY3 小时前
用 useRef 管理 Worker 线程,让你的页面不再卡顿
javascript
码林鼠5 小时前
2026前端面试题(二)
前端
满栀5856 小时前
请求拦截、响应拦截、业务错误统一处理
前端·typescript·anti-design-vue
vx-程序开发7 小时前
django医院预约挂号系统---附源码23353
java·javascript·spring boot·python·eclipse·django·php
宿6747 小时前
tsconfig.node.json
前端·javascript·vue.js
小杨互联网7 小时前
Cursor 前端 Dist 自动化逆向框架:/fr 流水线 · 4 脚本 · Vite/Webpack
前端·webpack·自动化·前端逆向框架·ai前端逆向框架
瑞码空间7 小时前
Web应用的多端部署之道:浏览器 · Electron · Docker
前端·docker·electron·浏览器·web