TypeScript学习笔记

290 阅读7分钟

基本类型和对象类型

基础类型:number, string, null, undefined, symbol, boolean, void

const num: number = 123;
const teacherName: string = 'Dell';

对象类型:{}, [], Class, function

const teacher: {
  name: string;
  age: number;
} = {
  name: 'Dell',
  age: 18,
};

const numbers: number[] = [1, 2, 3];

class Person {}
const dell: Person = new Person();

const getTotal: () => number = () => {
  return 123;
};

const func1: (str: string) => number = (str) => {
  return parseInt(str, 10);
};

const func2 = (str: string): number => {
  return parseInt(str, 10);
};

类型注解和类型推断

// type annotation 类型注解:我们来告诉TS变量是什么类型
let count1: number;
count1 = 123;

// type inference 类型推断:TS自动去尝试分析变量的类型
let count2 = 123;

// 如果TS能够自动分析变量类型,我们就什么也不需要做了
const num1 = 1;
const num2 = 2;
const total = num1 + num2;

// 如果TS无法分析变量类型的话,我们就需要使用类型注解
function getTotal2(num1: number, num2: number) {
  return num1 + num2;
}

const total = getTotal2(1, 2);

和函数相关的类型

voidnever

// 函数定义和js中一样,主要有3种
function hello() {}
const hello1 = function () {}
const hello2 = () => {}

// 最后的number可以不写,可通过类型推断推断出来
// 但是为了安全起见,还是写上比较好。因为在做逻辑处理时可能代码写错。
function add(first: number, second: number): number {
  return first + second
}

// 常用函数类型还有void、never等
function sayHello(): void {
  console.log("hello")
}

// never: 函数永远不可能执行完或者会抛出异常时使用
function errorEmitter(): never {
  throw new Error() // while(true) {}
  console.log("123")
}

// 解构的类型注解
function add({ first, second }: { first: number; second: number }): number {
  return first + second
}
const total = add({ first: 1, second: 2 })

// 解构一个变量时要注意
function getNumber({ first }: { first: number }): number {
  return first
}

其他情况

any:任意类型

// 其他:Date类型等
const data = new Date()

// 其他的case: 比如用JSON.parse等函数时无法进行类型推断,要使用类型注解
interface Person {
  name: "string"
}
const rawData = '{"name":"Dell"}'
const newData = JSON.parse(rawData) // 类型推断无法使用,newDate为any类型
const newData2: Person = JSON.parse(rawData) // newDate2为Person类型

类型断言

有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你已经进行了必须的检查。

类型断言有两种形式。 其一是“尖括号”语法:

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

另一个为 as 语法:(当在 TypeScript 里使用 JSX 时,只有 as 语法断言是被允许的)

let someValue2: any = "this is a string"
let strLength2: number = (someValue as string).length

type-类型别名

类型别名也是一种类型,用一个单词代表可能比较复杂的类型声明,用关键字type表示。

示例:

type S = string
let a: S = 'a'

这里用S作为string的别名,使用方法和string一模一样。

别名不仅可以代表基本类型,它可以代表任意类型。示例:

type SA = string[] // 代表字符串数组
type Handler = (x: string) => void // 代表函数
type I = {
  // 代表接口
  name: string
  value: number
}
class C {
  name: string = 'a'
  value: number = 0
}
type D = C // 代表类

interface-接口

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

const getPersonName = (person: Person): void => {
  console.log(person.name);
};
const setPersonName = (person: Person, name: string): void => {
  person.name = name;
};

const person = {
  name: 'Jonas',
  age: 22,
};

getPersonName(person);
setPersonName(person, 'Mike');
getPersonName(person);

另外,接口也可以和类一样继承。

若使用变量的方式,可以在person中添加接口中未定义的属性:

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

const person = {
  name: 'Jonas',
  age: 22,
  sex: 'male'
};

getPersonName(person);

但如果使用字面量方式,就会报错,因为字面量会进行严格的检查。

getPersonName({
  name: 'Jonas',
  age: 22,
  sex: 'male' // 报错
});

class-类

和js有一点区别,比如下面的例子中必须要定义name的类型。

class Person {
  name: string;
  eat() {
    return `${this.name} is eating something`
  }
}
const jonas = new Person();
jonas.name = 'Jonas';
console.log(jonas.eat());

访问类型

  • public:允许在类的内外被使用
  • private:允许在类内被使用
  • protected:允许在类内及继承的子类中被使用

构造器-constructor

constructor是实例化时会自动执行的。

class Person {
  name: string;
  constructor(name: string) {
    this.name = name; // this.name是指类内定义的name,等号右边的name是构造函数传入的name
  }
  eat() {
    return `${this.name} is eating something`
  }
}
const jonas = new Person('Jonas');
console.log(jonas.eat());

构造器传参还可以简化:

class Person {
  // 传统写法
  // public name: string;
  // constructor(name: string) {
  //   this.name = name;
  // }
  // 简化写法
  constructor(public name: string) {
  }
  eat() {
    return `${this.name} is eating something`;
  }
}
const jonas = new Person('Jonas');

继承

class Student extends Person {
  study() {
    return `${this.name} is studing`;
  }
}
const mike = new Student('Mike');
console.log(mike.study());

如果子类中也有构造器,则需要在构造器中先调用父类的构造器才行:

class Student extends Person {
  constructor(public age: number) {
    super('Dell');
  }
}
const dell = new Student(29);

console.log(dell.age); // 29
console.log(dell.name); // Dell

即使父类没有构造器,在子类的构造器中也要调用super()

getter和setter

class Person {
  constructor(private name: string) {}
  get getName() {
    return this.name + ' the boy'; // 这里可以做一些保护变量的处理
  }
}

const jonas = new Person('Jonas');
console.log(jonas.getName); // Jonas the boy,getName后面不用加括号

一般这样使用:

class Person {
  constructor(private _name: string) {}
  get name() {
    return this._name + ' the boy';
  }
  set name(name: string) {
    const realName = name.split(' ')[0];
    this._name = realName;
  }
}
const person = new Person('Jonas');
console.log(person.name); // Jonas the boy
person.name = 'Mike';
console.log(person.name); // Mike the boy

若要想设计成只读,处理只设置getter不设置setter这个方法外,还可以使用readonly修饰符。

class Person {
  public readonly name: string;
  constructor(name: string) {
    this.name = name;
  }
}

一个综合例子:单例模式

单例模式就是一个类只能有一个实例。

// 单例模式
class Demo {
  private static instance: Demo;
  private constructor(public name: string) {} // 把构造器设为private在类外就不能使用new来实例化了

  static getInstance(name: string) { // static就是直接挂在类上,而不是类的实例上
    if (!this.instance) {
      this.instance = new Demo(name);
    }
    return this.instance;
  }
}

const demo1 = Demo.getInstance('Jonas');
const demo2 = Demo.getInstance('Mike');
console.log(demo1.name); // Jonas
console.log(demo2.name); // Jonas

其他类型

主要有:联合、交叉、泛型、字面量类型等。

联合类型

可以是几种类型中的任意一种类型:

let temp: number | string = 123
temp = "456"

交叉类型

是几种类型合并成的新类型,拥有所有类型的属性:

interface IName {
  name: string;
}
type IPerson = IName & { age: number };
let person: IPerson = { name: '123', age: 123 };

字面量

const str: 'name' = 'name';
const number: 1 = 1;
type Directions = 'Up' | 'Down' | 'Left' | 'Right';
let toWhere: Directions = 'Left';

泛型

在定义时不确定类型,使用时才确定。比如我们定义一个函数,这个函数的返回值要和传入值的类型一致。这时我们就可以使用泛型。为函数添加一个泛型T,使用T传入值的类型。

function echo<T>(arg: T): T {
  return arg;
}

泛型有两种使用方法:

第一种是,传入所有的参数,包含类型参数:

const result1 = echo<string>('123'); // string
const result2 = echo<number>(123); // number

这里我们明确的指定了T是string类型,并做为一个参数传给函数,使用了<>括起来而不是()。

第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:

const result1 = echo('123'); // string
const result2 = echo(123); // number

注意我们没必要使用尖括号<>来明确地传入类型;编译器可以查看myString的值,然后把T设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。

function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}
const result3 = swap(['string', 123]); // [number, string]

约束泛型

在之前的echo函数中,如果我们想同时打印出arg的长度。 我们很可能会这样做:

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

这时程序会报错,因为传入的类型不一定有length属性。如果我们操作的是T类型的数组而不是T,那么是有length属性的,程序不会报错:

function echo<T>(arg: T[]): T[] {
  console.log(arg.length);
  return arg;
}

但除了数组,还有很多类型都具有length属性。那么如何解决呢?就需要用到泛型约束。
我们定义一个借口,含有length属性,然后使用extends

interface hasLength {
  length: number
}

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

这样,只有具有length属性的类型才能传入:

const str = echo('string');
const obj = echo({ length: 100, width: 200 });
const arr = echo([1, 2, 3]);
const num = echo(123); // 报错

泛型约束不仅可以用在函数中,还可以用在接口、类中。

内置类型

// global object
const a: Array<number> = [1, 2, 3];
const date = new Date();
date.getTime();
const reg = /abc/;
reg.test('abc');

// built-in object
Math.pow(2, 2);

// DOM and BOM
let body = document.body;
let allLis = document.querySelectorAll('li');
allLis.keys();

document.addEventListener('click', (e) => {
  e.preventDefault();
});

Utility Types

Pertial:使用了Partial后,所有属性都变成可选的了。

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

let viking: Person = { name: 'Viking', age: 20 };
type personPartial = Partial<Person>;
let viking2: personPartial = { name: 'Viking' };

image.png

Omit:忽略某些属性。

type personOmit = Omit<Person, 'name'>;
let viking3: personOmit = { age: 20 };

image.png

参考文章:
juejin.cn/post/694931… www.tslang.cn/docs/handbo…