TypeScript全攻略|青训营笔记

169 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的第5天

何为 TypeScript?

官方定义:添加了类型系统的JavaScript

个人理解:明确了变量类型的JavaScript,其定义类型的语法很像苹果的Swift 语言。

类型特点

静态类型

JavaScript 是动态类型,在运行阶段才会报错

TypeScript 是静态类型,在编译阶段就会报错

弱类型

是否允许隐式类型转换,可以分为强类型弱类型

TypeScriptJavaScript 都是弱类型,可以自动隐式类型转换。

TypeScript:

console.log(1 + '1');
// 打印出字符串 '11'

Python:

print(str(1) + '1')

总结

什么是 TypeScript?

  • TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。
  • TypeScript 是一门静态类型、弱类型的语言。
  • TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。
  • TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
  • TypeScript 拥有很多编译选项,类型检查的严格程度由你决定。
  • TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。
  • TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。
  • TypeScript 拥有活跃的社区,大多数常用的第三方库都提供了类型声明
  • TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。

基础

执行TypeScript文件:tsc hello.ts

function sayHello(person: string) {
   return 'Hello, ' + person;
}
​
let user = 'Tom';
console.log(sayHello(user));

原始数据类型

数据类型分为两种:原始数据类型和对象类型。

原始数据类型包括:

  • 布尔值
  • 数值
  • 字符串
  • null
  • undefined
  • Symbol (ES10)
  • BigInt

任意值(Any)

如果是普通类型的值,在赋值后不允许改变类型,但是任意值类型可以。

属性

在任意值上可以访问任何属性,也可以调用任何方法

⚠️若一个变量在声明时为指定类型,则默认被识别为任意值类型

在数组中使用Any

若在将数组定义为Any类型,那么数组中可以出现任意类型。

联合类型

表示取值可以为多种类型中的一种

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

属性

由于有多种类型的可能,TypeScript 不能确定变量是哪个类型,因此只能访问联合类型里所有类型共有的属性或者方法

function getString(something: string | number): string {
   return something.toString();
}

以上例子中,something 调用的方法必须是stringnumber 类型共同有的。

接口

继承接口时,属性不能多,也不能少,必须完全一致。

可选属性

可以在属性名后添加一个❓,这样该属性将变为可选属性

简单示例:

interface Person {
   namestring;
   age?: number;
}
​
let tomPerson = {
   name'Tom',
  // age: 25 //可写可不写
};

任意属性

通过添加 [propName:类型名]:类型名 来定义任意属性。

[] 中的是键,前面一个类型名指键的类型名,后面一个类型名指值的类型名。

interface Person {
   name: string;
   age?: number;
  [propName: string]: any;
}
​
let tom: Person = {
   name: 'Tom',
   gender: 'male'
};

只读属性

只读属性只能在创建的时候被赋值。一旦创建完只能读取不能更改。

interface Person {
   readonly idnumber;
   namestring;
   age?: number;
  [propNamestring]: any;
}
​
let tomPerson = {
   id89757,
   name'Tom',
   gender'male'
};

数组类型

最简单定义法:

let fibonacci: number[] = [1, 1, 2, 3, 5];`

数组泛型

使用数组泛型表示数组:

let fibonacci: Array<number> = [1, 1, 2, 3, 5];

接口

除非是表示类数组,一般不用这种方式。

interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

类数组

function sum() {
    let args: number[] = arguments;
  
}

arguments其实是一个类数组,应该用接口表示

function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

函数

声明

TS声明函数语法较为简单,与JS相差不大。

function sum(x: number, y: number): number {
    return x + y;
}

函数表达式

//在TS中,=>符号不是箭头函数,而是表示函数定义,左边是输入类型,右边是输出类型
let sum:(x:number, y:number) => number = function(x:number, y:number): number {
  return x+y;
};

接口应用

可以用接口来定义函数的形状,例如将mySearch 定义为SearchFunc 接口,这时

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

参数

可选参数

函数中的参数和属性一样,可以设为可选参数,同样,也是只需要在参数名后面加一个?

⚠️要注意的是,一旦出现了可选参数,后面就不能出现必须参数,也就是说,可选参数要放在参数列表的最后

参数默认值

可以给参数添加默认值,添加了默认值后的参数默认被识别为可选参数,当然,加了默认值的参数不用必须放在参数列表的最后面

剩余参数

可以用运算扩展符(ES6)来获取函数中的剩余参数。

function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);

重载

重载允许一个函数接受不同数量或类型的参数,并作出相应的处理。

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

类型

类型别名

用于给类型起一个新名字。

type Name = string;   //用type使得Name成为string的别名
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name { //这里Name指的就是string
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

字符串字面量类型

用来约束取值只能是某几个字符串中的一个。

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'

类型断言

语法

值 as 类型

用途

1.将一个联合类型断言为其中一个类型

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function isFish(animal: Cat | Fish) {
    if (typeof (animal as Fish).swim === 'function') {
        return true;
    }
    return false;
}

2.将一个父类断言为更加具体的子类

class ApiError extends Error {
    code: number = 0;
}
class HttpError extends Error {
    statusCode: number = 200;
}

function isApiError(error: Error) {
  //将error类型断言为ApiError,否则Error类型没有code属性,会报错。
   if (typeof (error as ApiError).code === 'number'){	
    //if (error instanceof ApiError) { 
      //该处可以使用typeof,但是用instanceof更加合适,因为可以直接判断实例
      //但是如果ApiError为接口,那么不能使用instanceof
        return true;
    }
    return false;
}

3.将一个本来没有的属性添加到目标对象上

本来可能会报错,但是可以将对象类型断言为Any,这样就不会报错,因为Any属性可以访问任何属性。

⚠️除非非常确定该行为是正确的,否则不建议这么做。

(window as any).foo = 1;//将window对象添加foo属性
总结

只有AB 存在兼容关系,两者才可互相断言。

常用对象

文档

ES内置对象

  • Boolean
  • Error
  • Date
  • RegExp

DOM、BOM

  • Document
  • HTMLElement
  • Event
  • NodeList

TS进阶

元组(Tuple)

概念

数组内(不是Any类型)只能放相同类型的元素,但是元组可以放不同类型的元素。

示例

//定义一对值为string和number的元组
let tom:[string,number] = ['Tom', 25];

越界元素

添加越界元素时,类型会被限制为元组中每个类型的联合类型。

let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true); //true会被限制为string|number类型

泛型

概念

在定义函数、接口、类时,不预先定义类型,在使用的时候再指定类型。

语法

function createArray<T>(length: number, value: T): Array<T> {  //使用泛型,可以确保value与result类型相同,但又不用事先定好他们的类型,因为value的类型可能不确定。
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray<string>(3, 'x');

同样的,也可以为泛型定义多个类型参数,例如:

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

约束

如果参数是泛型,那么在函数内部不能随意操作它的属性/方法,因为你不知道它是什么类型,自然的,也就无法确定它有什么属性。

为了这个缺点,可以使用泛型约束

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

多个参数类型互相约束

function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = (<T>source)[id];
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };

copyFields(x, { b: 10, d: 20 });

泛型接口

interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

泛型类

泛型接口相似:

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

声明合并

若出现了两个相同名字的函数、接口或者类,那么它们会合并成一个类型。


\