目录
-
- [一、TypeScript 是什么](#一、TypeScript 是什么)
-
- [1.1 为什么需要它?](#1.1 为什么需要它?)
- [1.2 它解决了什么?](#1.2 它解决了什么?)
- [1.3 它怎么工作的?](#1.3 它怎么工作的?)
- [1.4 与 Vue 的关系](#1.4 与 Vue 的关系)
- 二、基础类型
-
- [2.1 string / number / boolean](#2.1 string / number / boolean)
- [2.2 array ------ 数组](#2.2 array —— 数组)
- 三、对象的类型定义:Interface
-
- [3.1 什么是 Interface](#3.1 什么是 Interface)
- [3.2 关于"多余属性"的两个情况](#3.2 关于“多余属性”的两个情况)
- [四、Interface 与 Type 的区别](#四、Interface 与 Type 的区别)
-
- [4.1 基础用法对比](#4.1 基础用法对比)
- [4.2 核心区别一览](#4.2 核心区别一览)
- [4.3 实战分工(黄金法则)](#4.3 实战分工(黄金法则))
- [五、类(Class)与抽象类(Abstract Class)](#五、类(Class)与抽象类(Abstract Class))
-
- [5.1 普通类(具体类)](#5.1 普通类(具体类))
- [5.2 抽象类(Abstract Class)](#5.2 抽象类(Abstract Class))
- [5.3 Interface、抽象类、具体类的终极对比](#5.3 Interface、抽象类、具体类的终极对比)
- [六、类与接口的关系:`extends` 与 `implements`](#六、类与接口的关系:
extends与implements) -
- [6.1 Interface 与 Interface:`extends`](#6.1 Interface 与 Interface:
extends) - [6.2 类与 Interface:`implements`](#6.2 类与 Interface:
implements) - [6.3 类与类(或抽象类):`extends`](#6.3 类与类(或抽象类):
extends) - [6.4 总览速查表](#6.4 总览速查表)
- [6.1 Interface 与 Interface:`extends`](#6.1 Interface 与 Interface:
- 七、函数参数与返回值的类型标注
-
- [7.1 基本写法](#7.1 基本写法)
- [7.2 箭头函数与回调](#7.2 箭头函数与回调)
- 八、初识泛型(Generic)
-
- [8.1 泛型是什么](#8.1 泛型是什么)
- [8.2 Vue 3 中的 `Ref<T>`](#8.2 Vue 3 中的
Ref<T>) - [8.3 多个泛型参数](#8.3 多个泛型参数)
- 九、开发最佳实践(怎么选?)
一、TypeScript 是什么
TypeScript(简称 TS)由微软开发,2012 年首次发布,创始人是 Anders Hejlsberg(也是 C# 和 Turbo Pascal 的创造者)。
它的本质是 JavaScript 的超集 ,通俗来说就是 JavaScript + 类型系统。
1.1 为什么需要它?
JavaScript 存在几个先天痛点:
- 运行时才报错 :代码不运行就发现不了
null取值错误。 - 参数无约束:函数本意加数字,传入字符串却变成拼接,结果完全错误。
- 对象结构模糊:接手一个对象,里面有什么字段全靠猜或翻文档。
1.2 它解决了什么?
| JavaScript 的问题 | TypeScript 的解决 |
|---|---|
| 变量类型随时可变 | 类型固定,赋值错误立刻报错 |
| 函数参数无约束 | 参数类型明确,传错立即红线 |
| 对象字段靠猜 | 接口定义一目了然,IDE 自动补全 |
| 重构时遗漏修改 | 全部标红,一个不漏 |
一句话:把运行时错误,提前到写代码的开发阶段发现。
1.3 它怎么工作的?
TS 不能直接在浏览器运行,需要编译:
typescript
// 你写的 .ts 文件
function add(a: number, b: number): number {
return a + b;
}
// 编译后变成 .js(类型全消失)
function add(a, b) {
return a + b;
}
核心特性:类型标注只在编译阶段存在,运行后全部擦除,不增加任何运行时负担。
1.4 与 Vue 的关系
Vue 3 本身用 TypeScript 编写,其 API(如 ref / computed)天然支持 TS。使用 TS 后,IDE 能精准提示 value 的类型,彻底杜绝拼写错误(如把 name 写成 nme)。
二、基础类型
2.1 string / number / boolean
typescript
let name: string = 'Alice';
let age: number = 25;
let isDone: boolean = false;
类型推导:如果声明时赋值,TS 会自动推导类型,不必手写。
typescript
let name = 'Alice'; // 推导为 string
// name = 123; // ❌ 报错
2.2 array ------ 数组
typescript
// 写法一
let names: string[] = ['Alice', 'Bob'];
// 写法二(泛型语法)
let scores: Array<number> = [90, 85];
// 联合类型数组(元素可以是多种类型)
let mixed: (string | number)[] = ['Alice', 25];
三、对象的类型定义:Interface
3.1 什么是 Interface
interface 是用来描述对象结构 的"蓝图"。它只存在于 TypeScript 编译阶段,编译后彻底消失,不产生任何 JS 代码。
typescript
interface User {
id: number;
name: string;
age?: number; // ? 表示可选属性
}
// 使用
const user: User = { id: 1, name: 'Alice' }; // age 可省略
注意 :Interface 只能描述"长什么样",绝对不能包含具体逻辑实现(如 console.log)。它内部的方法只能是"纯虚"的(只有签名,没有函数体)。
typescript
interface Animal {
eat(food: string): void; // ✅ 只有签名
// eat(food: string): void {} // ❌ 报错!不能有实现
}
3.2 关于"多余属性"的两个情况
Interface 约束对象时,有一个重要的坑:直接写对象字面量 与先赋值给变量,检查严格程度不同。
typescript
interface User { name: string; }
// 情况一:直接写(触发多余属性检查)❌ 报错
const a: User = { name: 'Alice', age: 25 };
// 情况二:先赋值给变量(结构类型检查)✅ 合法
const temp = { name: 'Alice', age: 25 };
const b: User = temp;
虽然情况二合法,但在实际开发中,强烈建议保持 Interface 与对象完全一致,不要把多余字段悄悄带进来。
四、Interface 与 Type 的区别
type(类型别名)是另一种定义类型的方式,与 interface 高度重叠,但各有侧重。
4.1 基础用法对比
typescript
// interface
interface User { name: string; }
// type
type User = { name: string; };
4.2 核心区别一览
| 能力 | interface | type |
|---|---|---|
| 定义对象结构 | ✅ 主力 | ✅ 可以 |
定义基本类型别名(如 string) |
❌ 不能 | ✅ type ID = string |
| 定义联合类型(` | `) | ❌ 不能 |
| 定义元组(固定长度数组) | ❌ 不能 | ✅ type Point = [number, number] |
| 同名自动合并 | ✅ 会合并 | ❌ 重名报错 |
| 扩展/继承 | extends(冲突报错,安全) |
&(交叉类型,冲突变 never) |
4.3 实战分工(黄金法则)
对象结构用
interface,联合/元组/基础别名用type。两者是"主体与零件"的互补关系,不是二选一。
typescript
// 用 type 定义零件
type Theme = 'dark' | 'light';
type UserId = string;
// 用 interface 搭主体
interface User {
id: UserId;
theme: Theme;
}
五、类(Class)与抽象类(Abstract Class)
与 interface 不同,class 是 ES6 引入的 JavaScript 真实实体 ,编译后依然存在。在 TypeScript 中,class 拥有双重身份 :既是一个值 (运行时可以 new 造对象),也是一个类型(编译时可以做类型检查)。
5.1 普通类(具体类)
普通类所有方法都有具体实现,可以直接 new 出实例。
typescript
class Dog {
name: string;
constructor(name: string) {
this.name = name;
}
bark(): void {
console.log('汪汪');
}
}
const dog = new Dog('旺财'); // ✅ 可以 new
5.2 抽象类(Abstract Class)
抽象类是用来被继承的半成品。它有两个特点:
- 不能直接
new实例化(因为它是毛坯房,不能住人)。 - 内部可以既有已实现的方法 (给子类复用),也有抽象方法(逼迫子类必须自己实现)。
typescript
abstract class Animal {
// 已实现的方法(子类直接用)
eat(): void {
console.log('吃东西');
}
// 抽象方法(只有签名,没有实现)
abstract makeSound(): void;
}
// const a = new Animal(); // ❌ 报错!抽象类不能 new
class Cat extends Animal {
// 必须实现 makeSound,否则 TS 报错
makeSound(): void {
console.log('喵喵');
}
}
const cat = new Cat();
cat.eat(); // 输出:吃东西(直接用父类的)
cat.makeSound(); // 输出:喵喵(自己被迫填的坑)
5.3 Interface、抽象类、具体类的终极对比
| 比较维度 | Interface | 抽象类 | 具体类 |
|---|---|---|---|
| 能否写具体逻辑(方法体)? | ❌ 绝对不能 | ✅ 部分可以 | ✅ 全部可以 |
能否被 new 实例化? |
❌ 编译后消失 | ❌ 不能(半成品) | ✅ 能(成品) |
| 能否强迫子类实现某方法? | ✅ 可以(但只是约束) | ✅ 可以(abstract 方法强制实现) |
❌ 不能 |
| 编译后是否存在? | ❌ 彻底消失 | ✅ 保留(JS 函数) | ✅ 保留(JS 函数) |
六、类与接口的关系:extends 与 implements
这是你之前追问的核心。在 TS 中,extends(继承) 和 implements(实现) 有严格的适用对象。
6.1 Interface 与 Interface:extends
Interface 可以继承另一个 Interface,且支持多继承。
typescript
interface Animal { name: string; }
interface Swimmer { swim(): void; }
// 单继承
interface Dog extends Animal { bark(): void; }
// 多继承(逗号分隔)
interface Frog extends Animal, Swimmer { jump(): void; }
特别能力 :Interface 甚至可以 extends 一个普通类,从而提取该类的实例类型结构。
typescript
class Car { brand: string = 'Tesla'; }
interface ElectricCar extends Car {
charge(): void;
}
// 此时 ElectricCar 自动包含 brand: string
6.2 类与 Interface:implements
类想要拥有 Interface 定义的形状,不能使用 extends ,必须使用 implements。
typescript
interface Flyable { fly(): void; }
// ❌ class Bird extends Flyable {} // 报错!
// ✅ 正确写法:
class Bird implements Flyable {
fly() { console.log('飞'); }
}
一个类可以 implements 多个接口。
6.3 类与类(或抽象类):extends
类与类之间只能使用 extends(单继承)。
typescript
// 继承普通类
class Parent { name: string; }
class Child extends Parent {}
// 继承抽象类(必须实现抽象方法)
abstract class Base { abstract init(): void; }
class Concrete extends Base { init() {} }
6.4 总览速查表
| 左侧(定义者) | 关键字 | 右侧(被依赖者) | 是否允许 |
|---|---|---|---|
| Interface | extends |
Interface / 类(提取类型) | ✅ 允许 |
| 类(Class) | extends |
普通类 / 抽象类 | ✅ 允许 |
| 类(Class) | extends |
Interface | ❌ 不允许(请改用 implements) |
| 类(Class) | implements |
Interface / 类型别名(对象形状) | ✅ 允许 |
七、函数参数与返回值的类型标注
7.1 基本写法
typescript
// 普通函数
function add(a: number, b: number): number {
return a + b;
}
// 无返回值(void)
function log(msg: string): void {
console.log(msg);
}
// 可选参数(?)
function createUser(name: string, age?: number): void {
console.log(name, age ?? '未知');
}
7.2 箭头函数与回调
typescript
const double = (n: number): number => n * 2;
// 回调函数作为参数
function process(arr: number[], callback: (item: number) => void): void {
arr.forEach(callback);
}
八、初识泛型(Generic)
8.1 泛型是什么
泛型(<T>)可以理解为**"类型的变量"或"类型的函数参数"**。
它的核心作用是:用一个占位符(如 T)代替具体类型,确保函数/类/接口中不同位置(如参数与返回值,或存与取)的类型始终保持一致。
typescript
// 没有泛型:只能为每种类型单独写一个函数
// 有泛型:一个函数搞定所有
function wrap<T>(value: T): T {
return value;
}
const a = wrap('hello'); // T 被推导为 string
const b = wrap(123); // T 被推导为 number
8.2 Vue 3 中的 Ref<T>
你在 Vue 中天天用的 ref,就是泛型的经典应用。
typescript
import { ref } from 'vue';
// 显式指定类型
const count = ref<number>(0);
// count.value 的类型被锁定为 number
// 复杂类型
interface User { name: string; }
const user = ref<User | null>(null);
// 使用时,TS 知道 user.value 可能是 User 或 null
什么时候必须手动写 <类型>?
当初始值无法推导出完整类型时,比如 null 或空数组 []:
typescript
// 必须手动指定
const todos = ref<TodoItem[]>([]);
// 否则 TS 会推导为 never[],永远无法往里 push 东西
8.3 多个泛型参数
typescript
function merge<T, U>(a: T, b: U): [T, U] {
return [a, b];
}
const result = merge<string, number>('Alice', 25);
// result 类型为 [string, number]
九、开发最佳实践(怎么选?)
在实际编码时,按照这个决策流来:
-
定义对象/类结构 (如 API 返回、组件 Props、Store 状态)?
→ 优先用
interface(错误提示清晰,可声明合并)。 -
定义联合类型(
|)、元组([a,b])或基础类型别名 ?→ 只能用
type。 -
需要提供代码复用(公共方法)并强制子类填空 ?
→ 用
abstract class。 -
在 Vue 3 组合式 API 中造数据 ?
→ 忘掉
class,用interface定义结构,用普通函数返回对象字面量{}(这对 Vue 的响应式系统最友好)。