【笔记】 typescript

官网: https://www.typescriptlang.org/
官方文档: https://www.typescriptlang.org/docs/

简介

TypeScript 是添加了类型系统的 JavaScript 的超集,可以编译成 JavaScript。

TypeScript 的应用非常广泛,最新的 Vue 和 React 均集成了 TypeScript。(这里推荐大家使用 Vue3,因为 Vue3 天然支持 TS)

另一方面,TypeScript 中有很多 ES 的语法:

在这里插入图片描述

💡 提示

其实 TypeScript 可以看成 JavaScript 的编译器(规则)。生搬硬套了 java 的一系列规则,然后跟原生的 js 做适配。
目的就是:

  • 规范 js 的写法
  • 检查代码(避免编码问题导致的运行时异常)

为大型开发、团队开发、更加深度的封装开发提供条件

然后老版就可以随便开人,或者不怕程序员跑路,而继任者看不懂代码了。

类型系统

从 TypeScript 的名字就可以看出来, 「类型」 是其最核心的特性。

弥补 JavaScript 没有类型概念,导致的类型混乱的现象。

let foo = 1;
foo.split(' ');
// js 运行时报错,导致线上 bug
// ts 编译时就会提示,无法通过编译

兼容:弱类型

ts 完全兼容 js,因此也是弱类型的

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

安装、编译

首先,需要下载 node.js

然后,命令行安装 ts,并验证

npm install -g typescript
$ tsc -v
Version 4.8.4

尝试编译一个 ts 文件

index.ts

let time: number = new Date().getDate();
let title: string = "hello world!";
console.log(`typescript: ${title} ${time}`);

编译命令

$ tsc index.ts
$ tsc index.ts  --outFile build/index.js

输出 index.js

var time = new Date().getDate();
var title = "hello world!";
console.log("typescript: ".concat(title, " ").concat(time));

数据类型

# boolean

let flag: blloean = false;

# number

let num: number = 1;

# string

let name: string = 'Tom';
// 模板字符串
let sentence: string = `Hello, my name is ${name}`;

# void

javascript 中没有空值(void)的概念;在 TypeScript 中,可以用 void 标识没有任何返回值的函数

function alertName(): void {
	alert('My name is Tom');
}

声明一个 void 类型的变量,一般没啥用,因为只能将它赋值为 underfinednull

let unsable: void = underfined;

# null、undefined

let u: undefined = undefined;
let n: null = null;

# any 任意值

如果是 any 类型,允许被赋值为任意类型

let item: any = 'seven';
item = 7;

在 any 上访问任何属性都是允许的

let tiem: any = 'hello';
console.log(item.name);
console.log(item.age);
item.setName('Tom');

⚠️ 需要注意的是, any 类型的处理也会被认为是 any 类型

let t_name: any = 'Tom';
let t_h = t_name.substring(0,1);
t_h = 1; // 编译通过
let t_name: string = 'Tom';
let t_h = t_name.substring(0,1);
t_h = 1; // error TS2322: Type 'number' is not assignable to type 'string'.

# xxxx[] 数组

let temp: number[] = [1,2,3,4]
let head: string[] = ["1", "2"]

函数(function)

在 TypeScript 中对函数的定义,需要把输入和输出考虑到:

函数声明 的写法

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

函数表达式 的写法

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

// 完整写法
let sum: (x: number, y: number) => number = function(x: number, y: number): number {
  return x + y;
}

# 可选参数

function show(firstName: string, lastName?: string): string {
  return firstName + (lastName && " "+lastName || "")
}
console.log(`"${show("Tom", "Cat")}"`); // "Tom Cat"
console.log(`"${show("Boob")}"`); // "Boob"

# 参数默认值

在 ES6 中,允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数

function show(firstName: string, lastName: string = 'Cat-default'): string {
  return firstName + (lastName && " "+lastName || "")
}
console.log(`"${show("Tom", "Cat")}"`); // "Tom Cat"
console.log(`"${show("Boob")}"`); // "Boob Cat-default"

# 剩余参数

ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数):

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

let a = [];
push(a, 1,2,3);
console.log(a); // [ 1, 2, 3 ]

# 函数重载

重载允许一个函数接收不同数量或类型的参数时,作出不同的处理。

如:(不推荐,有更好的

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

然而,这样不能够精确的表达,也不便与 TypeScript 的处理。于是 TypeScript 提供了下面的写法:

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

⚠️ 注意

TypeScript 会优先选择上面的函数作为匹配的定义。因此,精确的定义应该尽量放在上面。

类(class)

关于类的关键字,不少是 js 原生的

# constructor

# get、set

# static

# public

# private

关键字支持解释
constructorjs
get、setjs(es5)
staticts
publicts
privatets只是在 ts 层面做限制

e.g.

class Girl {
  // js 原生写法
  // public name: string;
  // private age: number;
  // constructor(name?: string, age?: number) {
  //   this.name = name || "";
  //   this.age = age || 0;
  // }
  // ts 简化写法
  constructor(public name?: string, private age?: number) {
  }
  static hello() {
      console.log("hello world!")
  }
  get b() {
    return "B";
  }
  set b(d) {}
}

Girl.hello();
let alis = new Girl("alis", 16);
alis.b = "diik";

编译结果

var Girl = /** @class */ (function () {
    // js 原生写法
    // public name: string;
    // private age: number;
    // constructor(name?: string, age?: number) {
    //   this.name = name || "";
    //   this.age = age || 0;
    // }
    // ts 简化写法
    function Girl(name, age) {
        this.name = name;
        this.age = age;
    }
    Girl.hello = function () {
        console.log("hello world!");
    };
    Object.defineProperty(Girl.prototype, "b", {
        get: function () {
            return "B";
        },
        set: function (d) { },
        enumerable: false,
        configurable: true
    });
    return Girl;
}());
Girl.hello();
var alis = new Girl("alis", 16);
alis.b = "diik";

# abstract 抽象

抽象类不允许被实例化:

abstract class Animal {
  public constructor(public name: string){}
  public abstract eat(): void;
}

let a = new Animal("Jack");

// error TS2511: Cannot create an instance of an abstract class.

实例化抽象类

abstract class Animal {
  public constructor(public name: string){}
  public abstract eat(): void;
}

class Cat extends Animal {
  public hello(): void {
    console.log("miao~~~");
  }
  public eat(): void {
    console.log(`${this.name} is eating.`);
  }
}

let a = new Cat("Jack");

a.hello(); // miao~~~
a.eat(); // Jack is eating.

接口(Interfaces)

⭐️ 官方文档:https://www.typescriptlang.org/docs/handbook/interfaces.html

在 TypeScript 中,我们使用 Interface 来定义一个接口类型的对象。

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。接口的作用就是为这些类型命名和为实现代码定义契约。

interface Person {
	name: string;
	age: number;
}

let tom: Person = {
	name: 'Tom',
	age: 25
};

# 多余类型检查(Excess Property Checks)

实现如果多了属性是不允许的

let tom: Person = {
	name: 'Tom',
	age: 25,
  price: 2, 
  // error TS2322: Type '{ name: string; age: number; price: number; }' is not assignable to type 'Person'.
  // Object literal may only specify known properties, and 'price' does not exist in type 'Person'.
};

tom.title = '1'; // error TS2339: Property 'title' does not exist on type 'Person'.

实现如果少了属性也是不允许的

let tom: Person = { // error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.
	name: 'Tom',
};

# 可选属性(Optional Properties)

💡 如果希望实现的属性是可选的,可以如下写法:

interface Person {
	name: string;
	age?: number;
}

let tom: Person = {
	name: 'Tom',
};

# 只读属性(Readonly properties)

interface Point {
  readonly x: number;
  readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
// Cannot assign to 'x' because it is a read-only property.

数组有 ReadonlyArray<T> 这个标识符

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;

ro[0] = 12; // error!
// Index signature in type 'readonly number[]' only permits reading.

ro.push(5); // error!
// Property 'push' does not exist on type 'readonly number[]'.

ro.length = 100; // error!
// Cannot assign to 'length' because it is a read-only property.

a = ro; // error!
//The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.

只读数组类型(ReadonlyArray)赋值给非只读类型时需要转换

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
let b: number[] = ro as number[];

# 索引类型(Indexable Types)(又称:任意类型)

可以指定某种类型的索引(签名),其值是什么类型。

默认的签名有两种类型: string, number

string 签名
/*
[prop: string]: number 的意思是:
	1. A 类型的对象可以有任意属性签名
	2. string 指的是对象的键都是字符串类型的
	3. number 则是指定了属性值的类型。
	4. prop 类似于函数的形参,是可以取其他名字的。
*/
interface A {
    [prop: string]: number;
}

const obj: A = {
    a: 1,
    b: 3,
};
number 签名
interface B {
    [index: number]: string;
}

const arr: B = ['suukii'];
⚠️ 坑:其他类型的属性

像下面的写法是不成立的:

interface Person {
    name: string;
    age?: number;
    [prop: string]: string;
}

// Property 'age' of type 'number' is not assignable to string index type 'string'.

需要改为:

interface Person {
    name: string;
    age?: number;
    [prop: string]: string | number;
}

但这需要注意,正如一般的联合类型一样,它是会限制对象只能使用公共的属性和方法。换而言之,随便增加联合类型的种类,可能会导致原来正确的编译报错

# 函数类型(Function Types)

如果要定义一个函数的接收值、返回值,可以用 type 定义,也可以用这里的 interface 定义:

interface 定义

interface createArray {
  <T>(length: number, ...values: T[]): Array<T>;
}
let createArrayA: createArray = function<T>(length: number, ...values: T[]) {
  let result: T[] = [];
  for (let i=0; i<length; i++) {
    result[i] = values[i];
  }
  return result;
}
  
createArrayA<string>(3, 'x'); // ['x', undefined, undefined]

type 定义

type createArray = <T>(length: number, ...values: T[]) => Array<T>;
// interface createArray {
//   <T>(length: number, ...values: T[]): Array<T>;
// }
let createArrayA: createArray = function<T>(length: number, ...values: T[]) {
  let result: T[] = [];
  for (let i=0; i<length; i++) {
    result[i] = values[i];
  }
  return result;
}
  
createArrayA<string>(3, 'x'); // ['x', undefined, undefined]

# 接口的实现(implements)

interface Alarm {
  alert(): void;
}

abstract class Door {}

class SecurityDoor extends Door implements Alarm {
  alert(): void {
    console.log("SecurityDoor alert");
  }
}

class Car implements Alarm {
  alert() {
    console.log("Car alert");
  }
}

# 接口的合并

接口允许有重名

interface Alarm {
  price: number;
}
interface Alarm {
  weight: number;
}

// 相当于 
interface Alarm {
  price: number;
  weight: number;
}

类型推论(Type Inference)

如果没有明确的指定类型,那么 TypeScript 会依据类型推论(Type Inference)的规则推断出一个类型。

  1. 如果定义时被赋值,其类型会继承值的类型

    let t_name= 'Tom';
    t_name = 7;
    
    // error TS2322: Type 'number' is not assignable to type 'string'.
    
  2. 如果定义时没有被赋值,其类型被推断为 any 类型

    let t_name
    t_name = 'Tom';
    t_name = 7;
    

类型断言(Type Assertion)

类型断言可以手动来指定一个值的类型

value as type
or 
<type>value

⚠️ 注意

在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 value as type 的写法。

故建议使用这种更加通用的写法。

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

function handle(animal: Cat | Fish) {
  if(typeof (animal as Cat).run == 'function') {
    (animal as Cat).run();
  } else if(typeof (animal as Fish).swim == 'function') {
    (animal as Fish).swim();
  }
  return animal.name;
}

断言的时候能否对接口使用 instanceof 判断?

不行。

但是可以对接口(interface)的实现(implements)使用 instanceof 判断:

interface Animal{
  name?: string;
  age: number;
}

class Human implements Animal {
  age = 0;
  speak()
  {
    console.log("hello world!");
  }
}

let A = new Human();

if(A instanceof Human) {
  (A as Human).speak();
}


⚠️ 注意

类型断言 是 ts 的语法
类型转换 是 js 的语法

类型断言并不会导致类型转换。因此,当我们想转换类型的时候,还是要乖乖使用 js 的语法:

function toBoolean(something: any): boolean {
  // return something as boolean; // 错误写法
  return Boolean(something); // 正确写法
}

toBoolean(1);

💡 提示

类型声明比类型断言更加严格,因此尽量使用类型声明,而不是使用类型断言:

interface Animal {}
class Cat implements Animal {}
const animal1: Animal = new Cat();    // 类型声明 (👍🏻推荐)
const animal2 = new Cat() as Animal;  // 类型断言

联合类型(Union Types)

联合类型(Union Types)标识取值可以为多种类型中的一种。(中间以 | 分割)

let t_name: string | number = 'Tom';
t_name = 1;
t_name = 'Jim';

⚠️ 当 TypeScript 不能确定一个联合类型的变量到底是哪一个类型的时候,我们只能访问此联合类型的共有属性或方法

function getLength(something: string | number): number {
  return something.length;
  // error TS2339: Property 'length' does not exist on type 'string | number'.
  // Property 'length' does not exist on type 'number'.
}

只有当联合类型被推断出确定的类型后,才能使用该类型特有的方法:

let a: string = "Tom";
let b: string | number = a;
console.log(b.length)

元组(Tuple)

let tom: [string, number] = ['Tom', 25]
tom[0] = 'jack';
tom[1] = 1;

tom[1] = 'jack'; // 报错,必须为 number 类型

当下标越界时,越界项目的类型必须是元组定义的类型的联合类型:

let tom: [string, number] = ['Tom', 25]
tom[0] = 'jack';
tom[1] = 1;

tom[2] = 'jack'; 
tom[3] = true; // 报错,必须为 number 或者 string 类型

泛型(Generics)

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

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

let arr: Array<string> = createArray<string>(4, 'x', 'y', 'z');

console.log(arr); // [ 'x', 'y', 'z', undefined ]
// function getCacheData(key: string): any {
//   return (window as any).cache[key];
// }

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

// const tom: Cat = getCacheData('tom');
// tom.run();

function getCacheData<T>(key: string): T {
  return (window as any).cache[key];
}

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

const tom = getCacheData<Cat>('tom');
tom.run();

# 多个泛型

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;
}

# 泛型接口

interface CreateArrayFunc {
  <T>(length: number, ...values: T[]): Array<T>;
}

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

createArray<string>(3, 'x'); // ['x', undefined, undefined]

或者把泛型 T 提前,这样在声明泛型引用的时候就要指定 T 的实际类型了:(推荐

interface createArray<T> {
  (length: number, ...values: T[]): Array<T>;
}
let createArrayA: createArray<string> = function<T>(length: number, ...values: T[]) {
  let result: T[] = [];
  for (let i=0; i<length; i++) {
    result[i] = values[i];
  }
  return result;
}
  
createArrayA(3, 'x'); // ['x', undefined, undefined]

# 泛型类

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;
}

# 默认类型

在 TypeScript 2.3 以后,可以为泛型中的类型参数指定默认参数。 当使用泛型时,没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

function createArray<T=string>(length: number, ...values: T[]) {
  let result: T[] = [];
  for(let i=0; i<length; i++) {
    result[i] = values[i];
  }
  return result;
}

关键字:type 定义类型别名

// function getName(n: string | (() => string)): string {
//   if(typeof n === 'string') {
//     return n;
//   } else {
//     return n();
//   }
// }

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
  if(typeof n === 'string') {
    return n;
  } else {
    return n();
  }
}

关键字:type 定义字符串字面量

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

let ele: Element | null = document.getElementById("hello");
if(ele) {
  handleEvent(ele, "click");
}

💡 提示

是的,类型别名和字符串字面量的定义都是使用 type 关键字

关键字:enum 枚举

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"]===0); // true
console.log(Days["Mon"]===1); // true
console.log(Days["Tue"]===2); // true
console.log(Days["Sat"]===6); // true

console.log(Days[0]==="Sun"); // true
console.log(Days[1]==="Mon"); // true
console.log(Days[2]==="Tue"); // true
console.log(Days[6]==="Sat"); // true

看编译后的 js 代码:

var Days;
(function (Days) {
    Days[Days["Sun"] = 0] = "Sun";
    Days[Days["Mon"] = 1] = "Mon";
    Days[Days["Tue"] = 2] = "Tue";
    Days[Days["Wed"] = 3] = "Wed";
    Days[Days["Thu"] = 4] = "Thu";
    Days[Days["Fri"] = 5] = "Fri";
    Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
;
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

关键字:readonly 只读

class Animal {
	readonly name;
	public constructor(name: string) {
		this.name = name;
	}
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骆言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值