速通Typescript

前言

简介

  • 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 = 100
    
    type 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值