TypeScript入门篇(一)

941 阅读11分钟

一、TS抓紧学起来

  1. TS是JS的超集,是建立在JS上的一门语言。
  2. TS可以使用JS之外的扩展语法、面向对象、静态类型。
  3. TS基础语法 -- 爬虫功能开发 -- TS语法进阶 -- 项目接口开发 -- TS高级语法 -- 项目代码重构 -- 项目前端开发 -- 总结

包含知识点:静态类型、类型注解、类型推断、泛型、类型定义文件、模块化、打包编译、装饰器、Metadata(元数据)、设计模式

二、TS基础语法

1. TS的定义

推荐使用VSCode编辑器。因为TSVSCode都是微软推出的。VSCode做了很多对TS的适配。

参考文档:

  1. www.tslang.cn/docs/home.h…
  2. www.typescriptlang.org/

定义:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.TS是JS的超集,被编译为JS之后才可以运行。TS是静态代码类型

// ts文件
let a = 123
a = '123'  // 不能将类型“"123"”分配给类型“number”。因为其为静态类型,因此不能从number类型变为string类型。

标准写法
let a: number = 123
interface Person {
  name: string
}

const teacher: Person = {
  name: 'Fruit Bro'
}

2. TS的优势

参考文档:7 个不使用 TypeScript 的理由

interface Point {
  x: number,
  y: number,
}
function tsDemo (data: Point) {
  return Math.sqrt(data.x ** 2 + data.y ** 2)
}

tsDemo({x: 1, y: 2})

优势:

  1. 开发过程中,发现潜在问题。
  2. 更友好的编辑器自动提示。
  3. 代码语义更清晰易懂。

3. TS基础环境搭建

参考文档:

  1. nodejs.org/
  2. nodejs.cn/

// 安装TS
npm install typescript -g 

tsc demo.ts 就生成了demo.js文件
node demo.js // 运行生成的文件

// 转化工具 简化上述过程
npm install -g ts-node
// 运行
ts-node demo.ts

4. TS静态类型理解

// count为number类型之后,会具备number类型的所有属性和方法
const count: number = 2020 

count.提示的都是number类型所对应的方法,如下图:

// 自定义类型

interface Point {
  x: number,
  y: number,
}

const point: Point = {
    x: 1,
    y: 2
}

因此point变量具备Point所有的属性和方法。

总结:我们看到一个变量是静态类型,不仅仅意味着这个变量的类型不能修改,还意味这个变量的属性和方法已基本确定

5. TS基础类型和对象类型

  1. 基础类型
let count: number = 123
const personName: string = 'style'

还有null, undefined, symbol, boolean, void

  1. 对象类型
const teacher: {
  name: string,
  age: number,
} = {
  name: 'fruit',
  age: 18,
}
// 数组
const numbers: number[] = [1, 2, 3]

// 类
class Person {}
const fruit: Person = new Person() // fruit必须是个person类

// function, 返回值为number的函数
const getTotal: () => number = () => {
  return 123
}

6. TS类型注解(type annotation)和类型推断(type inference)

let count: number;

如上所示,显式声明变量类型的写法,称为类型注解

let countInfernce = 123
const firstNumber = 1
const secondNumber = 2
const total = firstNumber + secondNumber

在我们写的代码中,变量并没有使用类型注解,但并未报错,就是因为TS进行了类型推断。

如上所示,我们并未定义变量类型,TS会自动的去尝试分析变量的类型,称为类型推断

如果TS会自动分析变量类型,我们就什么都不需要做了。 如果TS无法分析变量类型的话,我们就需要使用类型注释。

function getTotal(firstNumber: number, secondNumber: number) {
  return firstNumber + secondNumber;
}

const total = getTotal(1, 2) // 此时就不需要写类型注解了

如上所示,此时是需要些类型注解的。

7. 函数相关类型

function add(first: number, second: number): number { // 定义返回类型
  return first + second
}
const total = add(1, 2)

// 如果不定义返回类型,如下写法不会报错
function add(first: number, second: number) { // 未定义返回类型
  return first + second + '' // 返回字符串
}
const total = add(1, 2)
function sayHello (): void { // 无返回值void
  console.log('hello')
}

TS中常用的返回值类型为:JSX.Element

// never:这个函数永远不会执行到最后,如下两种情况
function errorEmitter (): never {
  throw new Error()
  console.log('123')
}
function errorEmitter (): never {
  while(true) {}
}

解构赋值的类型写法:

情况1:
function add({ first, second }: {first: number, second: number}): number {
  return first + second
}
const total = add({first: 1, second: 2})

情况2:
function getNumber ({first}: {first: number}) {
  return first
}
const count = getNumber({first: 1})

8. TS基础语法

基础类型 boolean、number、string、void、symbol、undefined、null

对象类型 {}、function、[]、class

上图说明,如果声明和赋值放在两行写,则无法推断出变量类型。因此就需要类型注解。

函数写法1
const func = (str: string): number => {
  return parseInt(str, 10)
}
函数写法2
const func: (str: string) => number = (str) => {
  return parseInt(str, 10)
}

如上图,提示了函数的类型。
如上图,可以不用写返回值,可以用类型推断。

如上图,自动推断出Date类型。

const rawData = '{"name": "fruit"}'
const newData = JSON.parse(rawData) // JSON.parse返回的内容并不能帮助TS推断newData的类型

如下图,newData为any

因此可以改为如下写法:

interface Person {
  name: string
}
const rawData = '{"name": "fruit"}'
const newData: Person = JSON.parse(rawData)

更多类型

let temp: number | string = 123 // temp变量有可能为number或string
temp = '456'

9. TS数组和元组

元组:规定了每一个元素的类型。数量个数有限的数组,同时每一项的类型又是固定的形式

数组例子

数组有两种定义方式

方法一
let list: number[] = [1, 2, 3]
方法二
let list: Array<number> = [1, 2, 3]

基础类型

// 全部为number类型
const numberArr: number[] = [1, 2, 3]
// 为number或string
const numberArr: (number | string)[] = [1, '2', 3]
// 全部为string类型
const stringArr: string[] = ['a', 'b', 'c']
// 全部为undefined类型
const undefinedArr: undefined[] = [undefined]

对象类型

const objectArr: {name: string, age: number}[] = [{name: 'fruit', age: 18}]

也可以用类型别名

// type alias 类型别名,使用type关键字来定义
type User = {name: string, age: number}

const objectArr: User[] = [{name: 'fruit', age: 18}]

// 使用class
class Student {
  name: string;
  age: number;
}
const objectArr: Student[] = [
  new Student(),
  {  // 数据结构与Student保持一致也可以
      name: 'fruit', 
      age: 18
  }
]

元组例子

// 元组 tuple
const teacherInfo: [string, string, number] = ['Fruit', 'male', 18];

元组经常用于CSVExcel等类型的文件中

// 用元组处理CSV文件
const studentList: [string, string, number][] = [
  ['fruit', 'male',   18],
  ['bro',   'female', 26],
  ['jhon',  'female', 28],
]

10. TS的Interface接口

interface Person {
  name: string;
}

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

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

用类型别名也可以

type Person = {
  name: string;
}

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

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

interface接口与type(类型别名)的区别: type Person = string,type可以直接代表string,但interface只能代表一个对象或函数,没办法代表基础类型。

总结如下:

相同点: 1. 都可以描述一个对象或者函数 2.都允许用extends拓展

不同点: 1. type可以声明基本类型别名、联合类型、元组类型等 2. type 语句中还可以使用 typeof 获取实例的 类型进行赋值 3. 而inteface被多次声明时,能够声明合并

规范:能用接口的尽量用接口表示,实在没法表示再用类型别名。

关于interface和type区别的详细介绍可参考:Typescript 中的 interface 和 type 到底有什么区别

interface Person {
  readonly name: string; // 只读
  age?: number; // 可选属性
  [propName: string]: any; // 除了name和age外,还可以有其他属性,此属性名为字符串类型,属性值为任何类型
}

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

const setPersonName = (person: Person, name: string): void => {
  person.name = name; // name为只读后,将不能赋值
};

const person = {
  name: 'fruit',
}

getPersonName(person)
setPersonName(person, 'bro')
第一种
const person = {
  name: 'fruit',
  sex: 'male',
}

getPersonName(person)
第二种
const person = {
  name: 'fruit',
  sex: 'male',
}

getPersonName({
  name: 'fruit',
  sex: 'male', // 此种写法会报错,如下图
})

原因:当以字面量的形式(第二种写法)传入的时候,TS会对此对象进行强校验。以变量的形式传入,则不会这么严格(第一种写法)。

接口中有方法

interface Person {
  name: string;
  age?: number;
  [propName: string]: any; // 除了name和age外,还可以有其他额外属性,此属性名为字符串类型,属性值为任何类型
  say(): string; // 接口中有say方法,返回值为string类型
}

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

const person = {
  name: 'fruit',
  sex: 'male',
  say() { // 方法
    return 'say hello' // 返回值
  }
}

getPersonName(person)

interface Person {
  name: string;
  age?: number;
  [propName: string]: any;
  say(): string;
}

class User implements Person { // 类User应用Person接口

}

此时会报上述错误,因为Person接口中namesay方法是必传的,类里面必须具备接口的属性。

下面的写法是正确的

class User implements Person {
  name =  'fruit'
  say() {
      return 'hello fruit'
  }
}

接口的继承

interface Person {
  name: string;
  age?: number;
  [propName: string]: any;
  say(): string;
}

interface Student extends Person { // Student继承Person,并增加自己的方法study
  study(): string
}

1、ts类中只能继承一个父类 2、ts类中可以实现多少接口,使用(,)号分隔 3、ts接口中可以继承多个接口,使用(,)号分隔

interface InterfaceThree extends InterfaceOne,InterfaceTwo {
  
}

函数的类型声明定义

interface SayHi { // 函数的类型声明
  (word: string): string // 接收string类型的参数,同时返回值为string
}

const say: SayHi = (word: string) => {
  return word
}

总结:

  1. interface和type相类似,但不完全一致。
  2. readonly 只读属性、?可选参数、propName 额外属性、say()方法属性
  3. extends 接口继承
  4. 接口可直接定义具体的方法类型
  5. implements 用来做class类的属性约束
  6. 接口就是在我们开发的过程中帮助我们做语法提示的工具,编译后接口和类型全部剔除掉,并不会变成js代码

11. TS中类的定义与继承

普通类

class Person1 {
  name = 'fruit';
  getName() {
    return this.name;
  }
}

const person2 = new Person1();
console.log(person2.getName()); // fruit

普通类继承

class Teacher extends Person1 {
  getTeacherName() {
    return 'Bro';
  }
}

const teacher1 = new Teacher();
console.log(teacher1.getName()); // fruit
console.log(teacher1.getTeacherName()); // Bro

类的重写

class Teacher extends Person1 {
  getTeacherName() {
    return 'Bro';
  }
  getName() { // 重写getName
    return 'Fruit'
  }
}

const teacher1 = new Teacher();
console.log(teacher1.getName()); // Fruit
console.log(teacher1.getTeacherName()); // Bro

super关键字

class Teacher extends Person1 {
  getTeacherName() {
    return 'Bro';
  }
  getName() { // 重写getName
    return super.getName() + 'bro' // 使用super关键字,调用父类的方法
  }
}

const teacher1 = new Teacher();
console.log(teacher1.getName()); // fruitbro

super一般用来做什么:当一个类把父类的方法覆盖(重写)掉之后,此时还想调用父类的方法,此时可通过super来调用。

12. 类的访问类型和构造器

  1. 访问类型:public、protected、private

public:允许在类class的内、外(new出的实例中)被调用。

private: 允许在自身的类class内被使用。

protected: 允许在类内及继承的子类中使用。

// private protected public 访问类型
class Person {
  protected name: string; // 不写默认访问类型为public
  public sayHi() {
    console.log('hi');
  }
}

class Student extends Person {
  public sayBye() {
    this.name;
  }
}

const person = new Person();
person.name = 'fruit'; // name为protected,此时调用会报错
console.log(person.name);
person.sayHi();
  1. constructor构造器

constructornew实例化的时候会被自动执行

constructor语法:

写法1
// constructor
class Person {
  // 传统写法
  public name: string;
  constructor(name: string) {
    this.name = name;
  }
}
// constructor在new实例化的时候会被自动执行
const person = new Person('fruit');
console.log(person.name);

写法2
class Person {
  // 简化语法
  constructor(public name: string) {} 
}
// constructor在new实例化的时候会被自动执行
const person = new Person('fruit');
console.log(person.name);

constructornew实例化的时候会被自动执行

类的继承

子类要手动调用一下父类的构造器,还要按父类构造器的要求将参数传过去

class Person {
  constructor(public name: string) {}
}

class Student extends Person {
  constructor(public age: number) {
    super('fruit'); // 子类要手动调用一下父类的构造器,还要按父类构造器的要求将参数传过去
  }
}

const student = new Student(28);
console.log(student.age); // 28
console.log(student.name);// fruit
  1. 为什么我们要对一个类设置访问类型呢?访问类型的作用是什么呢?

13. 静态属性,Setter和Getter(重要)

Getter、Setter

class Person2 {
  // 一般private属性会写一个_
  constructor(private _name: string) {}
  get name() {
    // getter写法,可以确保name的安全性,如:对name进行加密
    return this._name + 'bro';
  }
  set name(name: string) {
    // setter写法。对name进行解析
    const realName = name.split(' ')[0];
    this._name = realName;
  }
}

const person = new Person2('fruit');
console.log(person.name); // fruitbro 执行getter,间接调用name属性
person.name = 'Fruit Bro';
console.log(person.name); // Fruitbro

单例模式:一个类,只允许通过这个类,获取一个这个类的实例

一般情况一下,一个类可以生成很多个实例,如下:

class Demo {

}

const demo1 = new Demo()
const demo2 = new Demo()
class Demo {
  private constructor() {}

}
// 限制外部不允许以new的方式创建实例
// new的时候要执行constructor,但此时它是private,因此是不允许的,就通过这种方式规避了new的情况
const demo1 = new Demo() // 此时new会报错

创建单例的写法:

class Demo {
  // 在Demo上创建instance属性
  private static instance: Demo;
  private constructor(public name: string) {}
  // static是指直接把这个方法挂在类上,而不是类的实例上
  static getInstance() {
    // instance为undefined时进行赋值
    if (!Demo.instance) {
      Demo.instance = new Demo('Fruit Bro');
    }
    return Demo.instance; // 若已创建过,则直接返回已创建的实例
  }
}
// 因此就可以直接调用了
const demo1 = Demo.getInstance();
const demo2 = Demo.getInstance();
console.log(demo1 === demo2); // true
console.log(demo1.name); // Fruit Bro
console.log(demo2.name); // Fruit Bro

以上方法就构造了一个单例。核心思想:只让new方法执行一次

readonly只读属性的用法:

// readonly
class Person {
  public readonly name: string
  constructor(name: string) {
    this.name = name
  }
}
const person3 = new Person('Fruit');
person3.name = 'Bro' // 此时赋值会报错
console.log(person3.name)

14. 抽象类

  1. 定义抽象类 abstract,抽象类被继承,不能直接用new实例化
  2. 抽象类主要是把一些通用性的东西做一下封装
  3. 抽象的实现方法,必须在继承子类中实现
abstract class Geom {
  width: number;
  getType() {
    return 'Geom';
  }
  abstract getArea(): number; // 抽象的实现方法,必须在继承子类中实现
}
// Circle、Square、Triangle都有getArea方法,则可以将其抽象出出来
class Circle extends Geom {
  getArea() { // // 抽象的方法,必须在继承子类中实现
    return 123;
  }
}

class Square {}
class Triangle {}

interface接口的抽象及其继承

// 接口和抽象类有些类似
interface Person { // 提出公共接口
  name: string
}
interface Teacher extends Person { // 接口继承
  teacherAge: number;
}

interface Student {
  age: number;
}

const teacher = {
  name: 'fruit'
};

const student = {
  name: 'bro',
  age: 18
};
const getUserInfo = (user: Person) => { // 只使用一个name属性就用Person接口,而不用(Teacher | Student),写法更简单
  console.log(user.name);
};

getUserInfo(teacher);
getUserInfo(student);