文章目录
前言
简介
- TypeScript在编译时通过静态分析可以检测出很多常见错误。通过 IDE 中基于类型的自动补全,还能改善开发体验和效率。
- TypeScript 的核心设计原则:“类型仅在编译期存在,运行时只有 Javascript。
- 现在的Javascript库基本上都自带了Typescript类型声明文件。
初始化
& tsc --init
& tsc --watch index.ts
& tsc --noEmitOnError --watch
- –init会初始化当前文件夹,生成tsconfig.json。
- –watch可以简写成–w。
- –noEmitOnError,当编译出错时不生成 js 文件。
基本用法
// 定义string变量。
let a: string
// 定义number变量。
let b: number
// 定义boolean变量。
let c: boolean
// 定义couont函数,它接收两个number类型参数,返回值是number类型。
function couont(x:number, y:number):number{
return x+y;
}
let d = -99 // TypeScript会推断出变量d的类型是数字
d = false // 警告:不能将类型“boolean”分配给类型“number”
let e: '李连杰' // e是一个字面量,它只能是字符串‘李连杰’
e = '李连杰' // 正确
e = '成龙' // 错误
常用类型
any
-
含义:任意类型,将变量类型限制为any,就意味着放弃了对该变量的类型检查。
// 明确的表示a的类型是 any —— 【显式的any】 let a: any // 以下对a的赋值,均无警告 a = 100 a = '你好' a = false // 没有明确的表示b的类型是any,但TS主动推断出来b是any —— 隐式的any let b //以下对b的赋值,均无警告 b = 100 b = '你好' b = false -
注意点:any类型的变量,可以赋值给任意类型的变量
let c:any c = 9 let x: string x = c // 无警告
unknown
-
含义:未知类型,需类型检查后再使用。
// 设置a的类型为unknown let a: unknown //以下对a的赋值,均符合规范 a = 100 a = false a = '你好' // 设置x的数据类型为string let x: string x = a //警告:不能将类型“unknown”分配给类型“string”// 设置a的类型为unknown let a: unknown a = 'hello' //第一种方式:加类型判断 if(typeof a === 'string'){ x = a console.log(x) } //第二种方式:加断言 x = a as string //第三种方式:加断言 x = <string>a -
操作any类型数据的任意属性都不会保存,unknown正好与之相反
let str1: string str1 = 'hello' str1.toUpperCase() //无警告 let str2: any str2 = 'hello' str2.toUpperCase() //无警告 let str3: unknown str3 = 'hello'; str3.toUpperCase() //警告:“str3”的类型为“未知” (str3 as string).toUpperCase() //无警告,使用断言强制指定str3的类型为string
never
-
含义:任何值都不是,不能有值undefined、 null、’'、 0都不行。
-
never一般是 TypeScript主动推断出来的。
// 指定a的类型为string let a: string // 给a设置一个值 a = 'hello' if (typeof a === 'string') { console.log(a.toUpperCase()) } else { console.log(a) // TypeScript会推断出此处的a是never,因为没有任何一个值符合此处的逻辑 } -
几乎不会使用never来定义变量,它的有一个特殊的用法,限制函数不需要返回值。
// 限制throwError函数不需要有任何返回值,任何值都不行,像undeifned、null都不行 function throwError(str: string): never { throw new Error('程序异常退出:' + str) // 只能通过throw的方式退出函数,不可以使用return }
void
-
含义:函数不返回任何值,调用者也不应依赖其返回值进行 任何操作!
// 无警告,函数有一个隐式的return undefined。 function logMessage(msg:string):void{ console.log(msg) } // 无警告 function logMessage(msg:string):void{ console.log(msg) return; } // 无警告 function logMessage(msg:string):void{ console.log(msg) return undefined } -
函数返回值undefined和void的核心区别:void会限制调用者使用返回值做任何操作!
function logMessage(msg:string):void{ console.log(msg) } let result = logMessage('你好') if(result){ // 此行报错:无法测试 "void" 类型的表达式的真实性 console.log('logMessage有返回值') }
object
-
含义:非原始类型,所有非原始类型,可存储:普通对象、数组、函数、日期、正则、自定义类实例。
- 不是string、不是number、不是boolean、不是symbol、不是null、不是undefined、不是bigint。
// a的值可以是任何【非原始类型】,包括:对象、函数、数组等 let a:object // 以下代码,是将【非原始类型】赋给a,所以均符合要求 a = {} a = {name:'张三'} a = [1,3,5,7,9] a = function(){} a = new String('123') class Person {} a = new Person() // 以下代码,是将【原始类型】赋给a,有警告 a = 1 // 警告:不能将类型“number”分配给类型“object” a = true // 警告:不能将类型“boolean”分配给类型“object” a = '你好' // 警告:不能将类型“string”分配给类型“object” a = null // 警告:不能将类型“null”分配给类型“object” a = undefined // 警告:不能将类型“undefined”分配给类型“object” -
object通常用来限制参数不能传原始类型,对于一般对象的限制我们通常采用以下方式,?表示属性可有可无。
// 限制person1对象必须有name属性,age为可选属性 let person1: { name: string, age?: number } // 含义同上,也能用分号做分隔 let person2: { name: string; age?: number } // 含义同上,也能用换行做分隔 let person3: { name: string age?: number } // 如下赋值均可以 person1 = {name:'李四', age:18} person2 = {name:'张三'} person3 = {name:'王五'} // 如下赋值不合法,因为person3的类型限制中,没有对gender属性 person3 = {name:'王五', gender:'男'} -
索引签名:用于描述对象中字段未知但是字段类型已知的结构,通过指定类型为any可以创建一个有任意字段和字段类型的对象。
// 限制person对象必须有name属性,可选age属性但值必须是数字,同时可以有任意数量、任意类型的其他属性 let person: { name: string age?: number [key: string]: any // 索引签名,完全可以不用key这个单词,换成其他的也可以 } // 赋值合法 person = { name:'张三' age:18, gender:'男' }
tuple
-
含义:一种特殊类型的数组,与普通数组不同,元组中的每个元素都有明确的类型和位置。
// 第一个元素必须是 string 类型,第二个元素必须是 number 类型。 let arr1: [string, number] // 第一个元素必须是 number 类型,第二个元素是可选的,如果存在,必须是 boolean 类型。 let arr2: [number, boolean?] // 第一个元素必须是 number 类型,后面的元素可以是任意数量的 string 类型 let arr3: [number, ...string[]] // 可以赋值 arr1 = ['hello',123] arr2 = [100,false] arr2 = [200] arr3 = [100,'hello','world'] arr3 = [100] // 不可以赋值,arr1声明时是两个元素,赋值的是三个 arr1 = ['hello',123,false]
enum
-
含义:一组命名常量,用于增强代码的可读性、可维护性。
- 数字枚举:是最长见的一种枚举,成员值会自动递增且具有反向映射的特点。
enum Direction { Up, Down, Left, Right } console.log(Direction) // 打印Direction会看到如下内容 /* { 0:'Up', 1:'Down', 2:'Left', 3:'Right', Up:0, Down:1, Left:2, Right:3 } */ // 反向映射 console.log(Direction.Up) console.log(Direction[0])- 字符串枚举:枚举成员的值是字符串。
enum Direction { Up = "up", Down = "down", Left = "left", Right = "right" }- 常量枚举:TypeScript 在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象。这可以减少生成的 JavaScript 代码量,并提高运行时性能。
const enum Directions { Up, Down, Left, Right } let x = Directions.Up;// 常量枚举 "use strict"; let x = 0 /* Directions.Up */;
自定义类型
联合类型
-
含义:通过管道(|)符号将变量设置多种类型,赋值时只能是设置的其中一种类型。
var val:string|number val = 12 console.log("数字为 "+ val) val = "Runoob" console.log("字符串为 " + val)var val; // 注意在定义变量时未赋值,所以不会进行类型推断 val = 12; console.log("数字为 " + val); val = "Runoob"; console.log("字符串为 " + val);function submitEvent(a:string | number) { console.log("提交事件"); }class Person { name: string | number; age: number; constructor(name: string | number, age: number) { this.name = name; this.age = age; } }// 可以包含字符串或数字的数组 let arr: (string | number)[] = ['a', 1]; type MixedArray = Array<string | number>;
type
-
功能:可以为任意类型创建别名,让代码更简洁、可读性更强,同时能更方便地进行类型复用和扩展。
-
用法:类型别名使用type关键字定义,type后跟类型名称,等号右边是类型内容。
type num = number; let price: num price = 100type sum = (a: number, b: number) => number; const sum = function (a: number, b: number): number { return a + b; }type Status = number | string type Gender = '男' | '女' function printStatus(status: Status) { console.log(status); } function logGender(str:Gender){ console.log(str) } printStatus(404); printStatus('200'); printStatus('501'); logGender('男') logGender('女')//面积 type Area = { height: number; //高 width: number; //宽 }; //地址 type Address = { num: number; //楼号 cell: number; //单元号 room: string; //房间号 }; // 定义类型House,且House是Area和Address组成的交叉类型 type House = Area & Address; const house: House = { height: 180, width: 75, num: 6, cell: 3, room: '702' };
接口
-
定义:是一种定义结构的方式,接口只能定义格式,不能包含任何实现。
-
接口合并:当在同一作用域内有多个同名的接口定义,TS会自动将它们合并成一个单一的接口,新接口包含所有声明中定义的成员。
-
定义类结构:
// PersonInterface接口,用与限制Person类的格式 interface PersonInterface { name: string age: number speak(n: number): void } // 定义一个类 Person,实现 PersonInterface 接口 class Person implements PersonInterface { constructor( public name: string, public age: number ) { } // 实现接口中的 speak 方法 speak(n: number): void { for (let i = 0; i < n; i++) { // 打印出包含名字和年龄的问候语句 console.log(`你好,我叫${this.name},我的年龄是${this.age}`); } } } // 创建一个 Person 类的实例 p1,传入名字 'tom' 和年龄 18 const p1 = new Person('tom', 18); p1.speak(3) -
定义对象结构:
interface UserInterface { name: string readonly gender: string // 只读属性 age?: number // 可选属性 run: (n: number) => void } const user: UserInterface = { name: "张三", gender: '男', age: 18, run(n) { console.log(`奔跑了${n}米`) } }; -
定义函数结构:
interface CountInterface { (a: number, b: number): number; } const count: CountInterface = (x, y) => { return x + y }
抽象类
-
定义:抽象类是无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法也可以写具体实现。
abstract class Package { constructor(public weight: number) { } // 抽象方法:用来计算运费,不同类型包裹有不同的计算方式 abstract calculate(): number // 通用方法:打印包裹详情 printPackage() { console.log(`包裹重量为: ${this.weight}kg,运费为: ${this.calculate()}元`); } } -
何时使用抽象类?
- 定义通用接口:为一组相关的类定义通用的行为(方法或属性)时。
- 提供基础实现:在抽象类中提供某些方法或为其提供基础实现,这样派生类就可以继承这些实现。
- 确保关键实现:强制派生类实现一些关键行为。
- 共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复。
泛型
-
泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性。
-
泛型函数
function logData<T>(data: T): T { console.log(data) return data } -
泛型接口
interface PersonInterface<T> { name: string, age: number, extraInfo: T } let p1: PersonInterface<string> let p2: PersonInterface<number> p1 = { name: '张三', age: 18, extraInfo: '一个好人' } p2 = { name: '李四', age: 18, extraInfo: 250 } -
泛型类
class Person<T> { constructor( public name: string, public age: number, public extraInfo: T ) { } speak() { console.log(`我叫${this.name}今年${this.age}岁了`) console.log(this.extraInfo) } } // 测试代码1 const p1 = new Person<number>("tom", 30, 250); // 测试代码2 type JobInfo = { title: string; company: string; } const p2 = new Person<JobInfo>("tom", 30, { title: '研发总监' , company: '发发发科技公司' }); -
泛型上限约束
interface LengthInterface { length: number } // 约束规则是:传入的类型T必须具有 length 属性 function logPerson<T extends LengthInterface>(data: T): void { console.log(data.length) } logPerson<string>('hello') // 报错:因为number不具备length属性 // logPerson<number>(100) -
默认泛型参数
function create<T extends string = "default">(value: T): T { return value; } create(); // T = "default" create("custom"); // T = "custom" create(123); // ❌ Error
扩展阅读
属性修饰符
- public:公共的,可以被,类内部、子类、类外部访问 。
- protected:受保护的,可以被类内部、子类访问。
- private:私有的,可以被类内部访问。
- readonly:只读属性,属性无法修改。
属性的简写
-
完整形式
class Person { public name: string; public age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } -
简写形式
class Person { constructor( public name: string, public age: number ) { } }
类型守卫
-
介绍:类型守卫是一种在运行时检查变量类型,并在编译期缩小其类型范围的机制。它让 TypeScript 能在条件分支中更精准的知道变量的类型,从而实现安全的属性访问和方法调用。
-
示例代码,虽然我们知道在某些情况下
x是字符串,但 TS 无法自动推断。function printLength(x: string | number) { console.log(x.length); // ❌ Error! number 没有 length 属性 } -
(一)typeof 类型守卫:用于处理原始类型
function handleValue(x: string | number) { if (typeof x === "string") { // TS 知道 x 是 string return x.toUpperCase(); } else { // TS 知道 x 是 number return x.toFixed(2); } } -
(二)instanceof 类型守卫:用于处理类和构造函数
class Dog { bark() { console.log("Woof!"); } } class Cat { meow() { console.log("Meow!"); } } function makeSound(animal: Dog | Cat) { if (animal instanceof Dog) { animal.bark(); // ✅ OK } else { animal.meow(); // ✅ OK } } -
(三)in 操作符守卫:用于检查属性是否存在
interface Bird { fly(): void; } interface Fish { swim(): void; } function move(animal: Bird | Fish) { if ("fly" in animal) { animal.fly(); // ✅ TS 推断为 Bird } else { animal.swim(); // ✅ TS 推断为 Fish } } -
(四)字面量类型守卫:用于处理字面量
type Status = "loading" | "success" | "error"; function handleStatus(status: Status) { if (status === "loading") { // status 类型缩小为 "loading" showSpinner(); } else if (status === "success") { // status 类型缩小为 "success" showData(); } } -
(五)自定义类型守卫函数
interface User { name: string; email: string; } interface Admin extends User { role: string; } // 自定义守卫函数 // user is Admin 是类型谓词,它告诉编译器当函数返回true时user的实际类型是Admin function isAdmin(user: User): user is Admin { return (user as Admin).role !== undefined; } function greet(user: User) { if (isAdmin(user)) { // TS 知道 user 是 Admin console.log(`Hello, ${user.role} ${user.name}`); } else { console.log(`Hello, ${user.name}`); } }
内置工具类型
-
TypeScript 提供了一系列内置的通用工具类型(Utility Types),它们都基于泛型实现,能极大简化常见的类型转换场景。
-
Partial - 把类型的所有属性变为可选。
-
Required - 把类型的所有属性变为必选。
-
Readonly - 把类型的所有属性变为只读。
-
Pick<T, K> - 从类型 T 中挑选出指定的属性 K(K 必须是 T 的属性名),组成新类型。
-
Omit<T, K> - 从类型 T 中排除指定的属性 K,剩下的属性组成新类型。
-
Record<K, T> - 创建一个对象类型,键的类型是 K,值的类型是 T,常用于定义字典 / 映射类型。
-
Exclude<T, U> - 从联合类型
T中排除掉U的类型,返回剩余的联合类型。 -
Extract<T, U> - 从联合类型 T 中提取可以 U 的类型。
-
ReturnType - 获取函数的返回值类型。
-
Parameters - 获取函数的参数类型,返回一个元组类型(每个元素对应一个参数的类型)。
-
索引访问类型
-
索引访问类型的核心语法很简单:
T[K],其中T是目标类型,K是要访问的索引(可以是属性名、数字索引、联合类型等)。// 定义一个对象类型 interface User { name: string; age: number; isAdmin: boolean; address: { city: string; street: string; }; } // 1. 提取单个属性的类型 type UserNameType = User["name"]; // 结果:string type UserAgeType = User["age"]; // 结果:number // 2. 提取嵌套属性的类型(链式访问) type UserCityType = User["address"]["city"]; // 结果:string // 3. 同时提取多个属性的类型(联合类型) type MixedType = User["name" | "age"]; // 结果:string | number -
经典用法1:设置函数的返回类型
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; // ✅ 编译器知道 key 是合法的 } -
经典用法2:复用已有类型的属性
// 定义一个对象类型 interface User { name: string; age: number; } // 场景:定义一个只更新 name/age 的函数参数类型 function updateUserInfo( // 只接收 name 或 age,值的类型和 User 中对应属性一致 info: { [K in "name" | "age"]: User[K]; } ) { // ... 业务逻辑 } // 合法调用 updateUserInfo({ name: "张三", age: 20 }); // 报错:类型不匹配(age 必须是 number) updateUserInfo({ name: "李四", age: "20" });
keyof关键字
-
介绍:属于索引类型查询的核心工具。它能在编译期提取一个类型的全部属性名(键),并返回一个字面量联合类型。
- 返回包含的属性:所有
public属性(包括可选属性和继承自父类/接口的属性),不包含private/protected属性。
type User = { name: string; age: number; }; type keys = keyof User; let a: keys = "name"; // 正常 let b: keys = "age"; // 正常 let c: keys = "id"; // 报错 - 返回包含的属性:所有
-
经典用法:实现类型安全的属性访问函数
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; // ✅ 安全!因为 K 一定是 T 的有效键 }function pluck<T, K extends keyof T>(objs: T[], key: K): T[K][] { return objs.map(obj => obj[key]); }
接口和type
- 相同点:interface 和 type 都可以用于定义对象结构,在定义对象结构时两者可以互换。
- 不同点:
- interface:更专注于定义对象和类的结构,支持继承、合并。
- type:可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合并。
接口和抽象类
- 相同点:都能定义一个类的格式(定义类应遵循的契约)。
- 不同点:
- 接口:只能描述结构,不能有任何实现代码,一个类可以实现多个接口。
- 抽象类:既可以包含抽象方法,也可以包含具体方法, 一个类只能继承一个抽象类。
参考资料
- https://www.typescriptlang.org/docs/handbook/2/generics.html#handbook-content
- https://www.typescriptlang.org/docs/handbook/2/everyday-types.html?spm=5176.28103460.0.0.72ab6308iuacxI
- https://www.typescriptlang.org/docs/handbook/2/classes.html?spm=5176.28103460.0.0.72ab6308iuacxI#abstract-classes-and-members
- https://cn.vuejs.org/guide/typescript/overview
- https://cn.vuejs.org/guide/typescript/composition-api
859

被折叠的 条评论
为什么被折叠?



