TS基础运用

2,729 阅读9分钟

【持续更新】

什么是typescript

Typescript是JavaScript的超集,对JavaScript进行了扩展,增加了一些JavaScript没有的属性。

为什么需要Typescript

简单来说就是因为JS是弱类型的,很多错误只有在运行时才会被发现。
而TS提供了一套静态检测机制,可以帮助我们在编译时就发现错误。

特点

  • 支持最新的JS新特性
  • 支持代码静态检查
  • 支持类型定义(枚举、泛型、类型转换、命名空间、声明文件、类、接口等)

基础数据类型

八种类型内置类型

let str: string = '捌玖';
let num: number = 25;
let bool: boolean = false;
let unde: undefined = undefined;
let n: null = null;
let obj: object = {test: 'ki'};
let bid: bidint = 1n;
let sym: symbol = Symbol('ki');

注意点

  • 默认情况下null和undefiend是所有其他类型的子类型,我们可以把null和undefined赋值给其他类型。例如:
let str = '捌玖';
str = null;
str = undefined;

如果我们在tsconfig.json指定了"strictNullChecks": truenullundefined只能赋值给void和他们本身的类型。

  • number和bigint不可以混用(这个点js一致,两个类型不能混用),虽然两个类型都表示数字

enum类型

使用枚举我们可以定义带名字的常量。使用枚举可以清晰地表达意图和常见一组有区别大的用例,TS支持数字的和基于字符串的枚举

  • 数字枚举
enum Direction {
    NORTH, // 0
    SOUTH, // 1
    EAST, // 2
    WEST // 3
}
let dir: Direction = Direction.NORTH;

在默认情况下,NORTH的初始值是0,其余的成员会从1开始自动增长。 当然我们也可以设置NORTH的初始值。

enum Direction {
    NORTH = 3, // 3
    SOUTH, // 4
    EAST, // 5
    WEST // 6
}
let dir: Direction = Direction.NORTH;
  • 字符串枚举
    在ts2.4中,允许我们使用字符串枚举。在一个字符串枚举中,每个字符串都必须使用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Direction {
    NORTH = 'north',
    SOUTH = 'south',
    EAST = 'east',
    WEST = 'west'
}
  • 常量枚举
    除了数字枚举和字符串枚举之外,还有一种特殊的枚举--常量枚举。它是使用const关键字修饰的枚举,常量枚举使用内联语法,不会为枚举类型生成任何JS。
const enum Direction {
    UP,
    DOWN
}
let dir: Direction = Direction.UP;

// 以上代码对应ES5代码如下
'use strict'
var dir = 0 /* UP */
  • 异构枚举
enum Char {
    A, B, C = "C", D = "D", E = 8, F
}

// 变异为ES5代码
"use strict";
var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
    Enum[Enum["B"] = 1] = "B";
    Enum["C"] = "C";
    Enum["D"] = "D";
    Enum[Enum["E"] = 8] = "E";
    Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));

其他类型

数组

  • 对数据项为内置类型的两种定义方式:
let arr1: string[] = ['1', '2', '3'];
let arr2: Array<string> = ['1', '2', '3'];
  • 定义联合类型数组
let arr3: (string | number)[] = ['1', 2];
  • 定义置顶对象成员的数组
interface Item {
    name: string;
    age: number;
}
let arr4: Item[] = [{name: '捌玖', age: 25}];
let arr5: Array<Item> = [{name: '捌玖', age: 25}]

函数

  • 函数声明
function sum(x: number, y: numer): number {
    return x + y;
}
  • 函数表达式
let sum2: (x: number, y: number) => numer = function(x: number, y: number): number {
    return number;
}
  • 用interface定义函数类型
interface ISum {
    (x: number, y: number): number;
}
  • 可选参数和默认值。可选参数后面不允许再出现必须参数
function add(x: number, y: number, z?: number = 0): number {
    return x + y + z;
}
  • 剩余参数
function push(arr: string[], ...strs: string[]) {
    arr.push(...strs);
}

Tuple元组

  • 元组定义
    数组一般有同种类型的值组成,但是我们需要在数组中存储不同类型的值,这是好我们可以使用元组。
    元组最重要的特性是可以限制数组元素的个数和类型。
let x: [string, number];
x = ['bajiu', 10]; // Ok
x = ['bajiu', 10, 10]; // Error
x = [10, 'bajiu']; // Error
  • 元组的结构赋值
let info: [string, number] = [25, 'bajiu'];
let [name, age] = info;

当我们解构数组元素的个数超过元组中元素的个数们,Typescript编译器会提示错误

let [name, age, id] = info;
// Tuple type '[number, string]' of length '2' has no element at index '2'.
  • 可选元素
    在定义元组类型是,我们也可以通过?来声明元组类型的可选元素,如下:
let optTuple = [string, boolean?];
optTuple = ['bajiu', true];
optTuple = ['bajiu'];
  • 剩余元素 剩余元素代表元组类型是开放的,可以有零个或多个额外的元素。
type RestTuple = [number, ...string[]];
let restTuple; RestTuple = [666, '123', '234'];
  • 只读类型
const point: readonly[number, number] = [1, 2];

// Cannot assign to '0' because it is a read-only property.
point[0] = 22;

// Property 'push' does not exist on type 'readonly [number, number]'.
point.push(0);

void

viod表示没有任何类型,和其他类型是平等关系,只能赋予nullundefined(在strictNullChecks未指定为true时)。声明一个void类型的变量没什么大用,一般也只有在函数没有返回值时去声明。

let a: void;
let b: number = a; // Error

方法没有返回值将得到undefined,但是我们需要定义成void类型,而不是undefiend类型,否则将报错

function fun(): undefined{
    console.log('bajiu')
}
fun(); // Error

never

never类型表示的是那些永不存在的值的类型。

  • 如果一个函数执行时排除异常,那么这个函数永远不存在返回值(异常中断了程序执行)
  • 函数中还行无限循环的代码(死循环),使程序永远无法运行到函数返回值的那一步
function err(msg: string): never {
    throw new Error(msg);
}

function loopInifinite(): never {
    while(true) {};
}

never类型同null和undefined,是任何类型的子类型,可以赋值给任何类型。 但是没有类型是never的子类型可以赋值给never类型(除了never本身之外),即使any也不可以赋值给never。

let ne: never;
let ne1: never = 123; // Error
let ne2: never = ne: // Ok

let an: any;
let ne3 = an;

any

在ts中,任何类型都可以被归为any类型,这让any类型成为了类型系统的顶级类型。

let an: any = 123;
an = '123';

在any上访问任何属性都是允许的,也允许调用任何方式

let anyThing: any = 'bajiu';
console.log(anyThing.myName);

变量如果在声明的时候,未指定起类型,那么它会被识别为任意值类型

let something;
something = 1;
something = '1';

unknown

为了解决any带来的,检查过于宽松的问题,所以ts 3.0引入了unknown类型。
unknown类型和any一样,所拥有类型都可以分配unknown

let notSure: unknown = 4;
notSure = '123';
nptSure = false;

如果不缩小类型,无法对unknown泪习惯执行任何操作,我们可以使用哦typeof、类型断言等方式来缩小未知范围

function getName() {
    return 'bajiu'
}
const name: unknown = {get: getName};
name.get(); // Error

unknown和any的区别

  • unknownany的最大区别是: 任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown 任何类型的值都可以赋值给它,但它只能赋值给unknownany
  • unknown必须缩小范围后,才能调用起对应的一些方法

interface

在面向对象语言中,接口是一个非常重要的概念,他是对行为的抽象。
ts中接口是一个非常灵活的概念,出了可用于对类的一部分行为进行抽象意外,也常用于对对象状态进行描述。

interface IPerson {
    name: string;
    age: number;
}
let tom: IPerson = {
    name: 'Tom',
    age: '15'
}

接口与类型别名的区别

实际上,在大多情况下两者效果是等价,但是在某些特定的场景下这两者还是有很大的区别的。

  • TS的核心原则之一是对值所具有的结构进行类型检查。而接口的作用就是为这些类型命名和为我们的代码或第三方代码定义数据类型
  • type(类型别名)会给一个类型起个新名字。type有时和interface很像,但是可以用于原始值(基本类型),联合类型,元组以及其他任何我们需要手写的类型。起别名不会创建一个新类型。

断言

类型断言

有的时候我们可能比TS更了解某个值的详细信息,这个时候我们需要通过类型断言这种方式告诉编辑器,“相信我,我知道自己在干什么”。它对运行不会造成影响,只在编译阶段起作用。

  • 尖括号语法
let str: unknown = 'bajiu';
let strLen: number = (<string>str).length;
  • as语法
let str: unknown = 'bajiu';
let strLen: number = (str as string).length;

非空断言

  • 忽略undefiend和null
function foo(str: string | undefined | null) {
    const onlyStr: string = str; // Error
    const ignoreUndAndNull: string = str!; // OK
}
  • 调用函数时忽略undefiend
type NumGeneratot = () => number;
function myFunc(numGenerator: NumGenerator | undefined) {
    // Object is possibly 'undefined'.(2532)
    // Cannot invoke an object which is possibly 'undefined'.(2722)
    const num1 = numGenerator(); // Error
    const num2 = numGenerator!(); //OK
}
  • 确定赋值断言
    TS2.7中引入的确定值断言,即允许在实例实行和变量属性后面放置!,从而告诉TS该属性会被明确地赋值。
let x1: numer;
setX1();
console.log(2 * x1); // Error
function setX1() {
    x1 = 10;
}

let x2!: numer;
setX12();
console.log(2 * x2); // OK
function setX2() {
    x2 = 10;
}

交叉类型

在ts中交叉类习惯时将多个类型合并为一个类型,通过&运算符可以将现有的多种类型叠加在一起成为一种类型

同名基础类型属性的合并

假设合并的多个类型的过程中,刚好出现某些类型相同的成员,但是对应的类型不一致

interface X {
    a: string;
    c: string;
}
interface Y {
    b: number;
    c: number;
}
type XY = X & Y;
// {a: string; b: number, c: never}

成员c的类型为string & number,即成员c的类型同时为number和string,显然这种类型时不存在的,所以混入后成员c的类型是never

同名非基础类型属性的合并

interface IA {
    x: { a: boolean; }
}
interface IB {
    x: { b: number; }
}
interface IC {
    x: { c: string; }
}
type ABC = IA & IB & IC;
// {x: {a: boolean; b: number; c: string;}};

类与readonly

class Person() {
    // constructor的参数name,一旦使用readonly修饰,那么该那么参数参数属性,this.name 不会报错
    // 如果没有readonly修饰,this.name会有错误提示
    // 有readonly外部也是无法修改的,内部普通函数也是无法修改的
    constructor(readonly name: string = 'bajiu') {
        this.name = name;
    }
}

参考资料

2021 typescript史上最强学习入门文章(2w字)
TypeScript面试题及答案收录[不断更新]
一份不可多得的 TS 学习指南(1.8W字)