TS基本使用

503 阅读10分钟

1. 概念:

  • 以JS为基础构建的语言
  • 一个JS的超集
  • 可以在任何支持JS的平台中执行
  • TS扩展了JS并添加了类型
  • TS不能被JS解析器直接执行
Typed JavaScript at Any Scale.
添加了类型系统的 JavaScript,适用于任何规模的项目
它强调了 TypeScript 的两个最重要的特性——类型系统、适用于任何规模。

1.1 TypeScript 的特性

    1. 类型是最核心的特性
  • JavaScript比较灵活,是动态类型语言,没有类型限制,可以隐式类型转换(弱类型),但也因此有了一定的风险。
类型系统按照「类型检查的时机」来分类,可以分为动态类型和静态类型。

动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。

JavaScript 是一门解释型语言,没有编译阶段,所以它是动态类型
  • TypeScrip 是静态类型
静态类型是指编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。
TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 `TypeScript` 是静态类型
  • TypeScript 是强类型
类型系统按照「是否允许隐式类型转换」来分类,可以分为强类型和弱类型

JS是弱类型,允许隐式转换
var a
a = 1
a = '33'

TS不允许隐式类型转换

以下这段代码不管是在 JavaScript 中还是在 TypeScript 中都是可以正常运行的,运行时数字 `1` 会被隐式类型转换为字符串 `'1'`,加号 `+` 被识别为字符串拼接,所以打印出结果是字符串 `'11'`
console.log(1 + '1');
// 打印出字符串 '11'
    1. 适用于任何规模 TypeScript 非常适用于大型项目——这是显而易见的,类型系统可以为大型项目带来更高的可维护性,以及更少的 bug

在中小型项目中推行 TypeScript 的最大障碍就是认为使用 TypeScript 需要写额外的代码,降低开发效率。但事实上,由于有[类型推论],大部分类型都不需要手动声明了。

日常使用TypeScript:在 VSCode 编辑器中编写 JavaScript 时,代码补全和接口提示等功能

    1. 与标准同步发展 TypeScript 的另一个重要的特性就是坚持与 ECMAScript 标准[10]同步发展

1.2 TS增加了什么

  • 类型
  • 支持ES的新特性
  • 强大的开发工具
  • 添加ES不具备的性特性
  • 丰富的配置选项

2. 基础类型

2.1 js的八种基本类型

  • 字符串(string)
  • 数字(number)
  • 布尔值(boolean)
  • 未定义(undefined)
  • 空值(null)
  • 对象(object)
  • 大整数(bigInt,ES6 新增)
  • 符号(symbol,ES6 新增)
// JS中`Symbol`类型是为了解决属性名冲突的问题
// 创建`symbol`变量最简单的方法是用`Symbol()`函数。`sysmbol`变量有两点比较特别:
// 1.  它可以作为对象属性名。只有字符串和 `symbol` 类型才能用作对象属性名。
// 2.  没有两个`symbol` 的值是相等的。

const symbol1 = Symbol(); 
const symbol2 = Symbol(); 
symbol1 === symbol2; // false

const obj = {}; 
obj[symbol1] = 'Hello'; 
obj[symbol2] = 'World';

2.2 对应的TS类型

let name: string = 'zs'
let age: number = 233
let isVisible: boolean = false
let a: undefined = undefined
let b: null = null
let obj: object = {
  name: 'cici'
}
let myInt: bigint = 100n   // 目标低于 ES2020 时,bigInt 文本不可用
let mySymol: symbol = Symbol('me')
2.2.1 Array
// 元素类型[]
let list: number[] = [1, 2, 3];
// Array<元素类型>
let list: Array<number> = [1, 2, 3];
2.2.2 Tuple 元祖
// 元组类型允许表示一个已知元素数量(长度)和类型的数组
let c: [number, string]
c = [10, 'ok']  // ok
c = ['ok', 10]  // error
2.2.3 Void 空值
// JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 `void` 表示没有任何返回值的函数:

// 以函数为例,就表示没有返回值的函数
function fn ():void {
  console.log("My name is Tom")
}

// 声明一个 `void` 类型的变量没有什么用,因为你只能为它赋予 `undefined` 和 `null`
let unusable: void = undefined
2.2.4 Null 和 Undefined
// 在 TypeScript 中,可以使用 `null` 和 `undefined` 来定义这两个原始数据类型
let u: undefined = undefined;
let n: null = null;


// 默认情况下`null`和`undefined`是所有类型的子类型。 
// 就是说你可以把`null`和`undefined`赋值给`number`类型的变量
// 注意:当你使用严格模式("strict": true,)`null`和`undefined`只能赋值给`void`和它们各自
let u: undefined = undefined;
let n: null = null;
2.2.5 Any
// any 表示是任意类型,一个变量设置类型为any相当于对该变量关闭了TS的类型检测 (不建议使用 any)
    let d: any = 4;
    d = 'hello';
    d = true;
    
// 声明变量不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any)
let e // 隐式声明
e = 678
e = false
// e 的类型是any,他可以赋值给任何变量,任何类型的值可以赋值给`any`
2.2.6 unknown
// unknown 实际上就是一个类型安全的any
// unknown类型的变量,不能直接赋值给其他变量
let notSure: unknown = 4;
let num: number
num = notSure  // 不能将类型“unknown”分配给类型“number”
// (1) 通过 typeof 
if (typeof notSure === 'number') {
  num = notSure
}
// (2) 通过 类型断言
num = notSure as number

// 任何类型的值都可以赋值给它,但它只能赋值给`unknown`和`any`
let notSure: unknown = 4;
let uncertain: any = notSure; // OK

let notSure: any = 4;
let uncertain: unknown = notSure; // OK

let notSure: unknown = 4;
let uncertain: number = notSure; // Error
2.2.7 enum 枚举类型

枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举
枚举是一种数据结构,使用枚举我们可以定义一些带名字的常量,清晰地表达意图或创建一组有区别的用例。 TS支持数字的和基于字符串的枚举

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
// 枚举成员会被赋值为从 `0` 开始递增的数字,我们可以像访问对象属性一样访问枚举成员:
console.log(Days.Sun) // 0
console.log(Days.Mon) // 1
......
console.log(Days.Sat) // 6

3. 类型断言

// 有些情况下,变量的类型对于我们来说是很明确,但是TS编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型
// 类型断言,可以用来告诉解析器变量的实际类型
// 语法: 变量 as 类型;   <类型>变量

let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;

let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;

// 我们还可以初始化枚举成员,那么该初始化成员后面的成员会在它的基础上自动增长`1`:
enum Days {Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days.Sun) // 1
console.log(Days.Mon) // 2
......
console.log(Days.Sat) // 7

// 字符串枚举很简单,直接赋给每个成员字符串字面量
enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

4. 类型推论

// 如果没有明确的指定类型,那么 `TypeScript` 会依照类型推论的规则推断出一个类型。
let n = 'one'
console.log(n)  // n: string

// `TypeScript` 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
// 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 `any` 类型而完全不被类型检查
let myNumber;
myNumber = 'seven';
myNumber = 7;

5.联合类型

// 联合类型表示取值可以为多种类型中的一种,使用 `|` 分隔每个类型。
let aa: string | number
aa = 'fine'
aa = 66

6. 交叉类型

// 交叉类型是将多个类型合并为一个类型。 
// 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,使用`&`定义交叉类型
interface A {
  name: string,
  age: number
}
interface B {
  name: string,
  gender: string
}

let a: A & B = { // OK
    name: "兔兔",
    age: 18,
    gender: "男"
};
// `a`既是`A`类型,同时也是`B`类型。

7.类型声明

  // 描述一个对象的类型
  // 类型声明:名称唯一,不能重复
  type myType = {
    name: string
    age: number
  }

  const obj: myType = {
    name: 'ccc',
    age: 33  // 这里的属性要和类型声明中一致,不能少,不能多
  }

8. 接口和抽象类

  • TypeScript 也能够用接口来明确的强制一个类去符合某种契约。 我们也可以用类去实现接口,这里使用关键字implements:
  // 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
  // 同时,接口也可以当成类型声明来使用
  interface yourType {
    name: string
    age: number
  }
  interface yourType {
    gender: string
  }

  const you: yourType = {
    name: 'youyou',
    age: 333,
    gender: '女'
  }
  
  // 可以用类去实现接口,这里使用关键字`implements`
    interface Title {
      title: string
    }
    class title implements Title {
      title: string = '兔子'
      age: number = 3  // 在实现接口的基础上,也可以添加其他的属性和方法
    }
    
  // 一个类可以实现多个接口:
    interface Age {
      age: number;
    }

    interface Title{
      title: string;
    }
    
    class title implements Title, Age{
      title: string = '兔兔';
      age: number = 18;
    }

// ts 数组类型不一致,可以定义接口来约束返回数据类型
interface Test{
  arr:string[]
}
const arr= reactive<Test>({arr:[]})

  // 接口可以在定义类的时候去限制类的结构
  // 接口中所有的属性都不能有实际的值
  // 接口只定义对象的结构,不考虑实际值
  // 在接口中,所有的方法都是抽象方法
  // 接口:规范,对类的限制
  // 接口和抽象类非常相似,
  // 区别是:(1)抽象类中的方法可以是普通方法,可以是抽象方法; 接口必须是抽象方法 (2)定义抽象:abstract;  定义接口:interface
  • 抽象类
  // 以abstract开头的类是抽象类
  // 抽象类和其他类区别不同,只是不能用来创建对象
  // 抽象类就是专门用来被继承的类
  abstract class Animal{
    name: string

    constructor (name: string) {
      this.name = name
    }  
    // 定义一个抽象方法
    // 抽象方法使用 abstract 开头,没有方法体
    // 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
    abstract sayHello(): void
  }
  
  class Dog extends Animal {
    sayHello() {
      // super.sayHello()
      console.log('!!!!!!')
    }
  }

  const dog = new Dog('xiaohei')
  console.log('dog', dog)

9. 泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

// 定义函数时使用泛型
function fn<T>(a: T): T {
  return a
}

// 可以直接调用具有泛型的函数
let res1 = fn(10)  // 不指定泛型,TS可以自动对类型进行判断
let res2 = fn<string>('hi') // 指定泛型

// 泛型可以同时指定多个
function fn1<T, S>(a: T, b: S):T {
  console.log(a, b)
  return a
}
fn1<string, number>('ok', 333)

// 定义类时使用泛型
class MyClass<T> {
  name: T
  constructor(name: T) {
    this.name = name
  }
  getName(x: T) {
    return x
  }
}
const getClass = new MyClass<string>('zs')

10. ! 和 ?.


type A = {
    name?: string,  // 这里的name属性是可选的
    age!: number   // age属性是必选的
}


// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
return new Error().stack.split('\n');

// 我们可以添加?操作符,当stack属性存在时,调用 stack.split。若stack不存在,则返回空
return new Error().stack?.split('\n');

// 以上代码等同以下代码, 感谢 @dingyanhe 的监督
const err = new Error();
return err.stack && err.stack.split('\n');


// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
new Error().stack.split('\n');

// 我们确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在
new Error().stack!.split('\n');

11. 扩展已经定义的类型

TypeScript 还允许你通过 declare module 的语法来扩展已有模块中定义的类型

declare module 'axios' {
  /**
   * Costom Axios Field.
   */
  export interface AxiosRequestConfig {
    redirect?: string
  }
}

通过declare global,可以拓展全局变量的类型和方法

declare global {
    interface IRequestData {
        error?: number
        data: any
        msg?: string
    }
}

参考:
ts.xcatliu.com/introductio…
juejin.cn/post/700317…
juejin.cn/post/684490…
github.com/e2tox/blog/…