最近要做一个博客系统,前台打算用VUETypescript完成。之前没有写过Typescript代码,这次借着这个项目,把Typescript做到基本掌握。本文会从最基础的内容开始讲起,最后会结合VUE写一些例子程序,期间遇到的各种坑也将一并记入。下面开始Typescript的学习把!

安装Typescript

NPM 安装 TypeScript

使用国内镜像:

1
npm config set registry https://registry.npm.taobao.org

安装 typescript:

1
npm install -g typescript

安装完成后使用 tsc 命令查看版本号:

1
2
tsc -v
Version 4.3.5

到此,证明Typescript安装成功。

Yarn 安装 TypeScript

1
yarn global add typescript

VSCode 配置 TypeScript 自动编译

首先,在项目目录执行初始化:

1
tsc --init

执行完成后会在项目目录下生成配置文件tsconfig.json,修改该配置文件中的"outDir": "./js",该选项指定编译成js文件的输出路径。然后执行:终端->运行任务->tsc:监视 xxxx/tsconfig.json。控制台成功监视后,我们再编写ts代码时,就会同时在设定的目录下生成对应的js代码了。注意:启动监视任务时,如果是Windows平台,需要首先把VSCode的默认终端设置为Power Shell或者命令行,否则监视程序会报错。有关配置文件的详细说明,可以参考tsconfig.json 文档

语法基础

类型注解

接下来,让我们看看TypeScript工具带来得高级功能:类型注解

1
2
3
4
5
6
(() => {
  function Greet(person: string, date: Date): void {
    console.log(`Hello ${person}, today is ${date.toDateString()}!`);
  }
  Greet('brein', new Date());
})();

在函数Greet中我们将第一个参数person注释为了string类型,将第二个参数date注释为了Date类型,同时设置了函数得返回值为void。这样我们就告诉了编译器,我们希望第一个参数是string类型,第二个参数是Date类型,函数必须返回void。那么当我们把传入参数做了修改会发生什么呢?我们变更调用,把第二个参数修改为:

1
Greet('brein', Date());

此时,编译器就会告诉我们:

1
类型“string”的参数不能赋给类型“Date”的参数。

这对于我们得的开发简直是太有用了,再也不用担心程序跑出莫名其妙的结果了。在最一开始就为我们做出了错误提示。这可以说是对JS的一个强力升级了。为什么拿出来单独说,第一个说,就是它太强大了,太好用了。下面我们就进入基础的学习吧。

常用语法

基础类型

  • 布尔值

    1
    2
    
    let isOk: boolean = false;
    isOk = true;
    
  • 数字

    1
    2
    3
    4
    
    let num1: number = 10; //十进制
    let num2: number = 0o12; //八进制
    let num3: number = 0b0101; //二进制
    let num4: number = 0xab10; //十六进制
    
  • 字符串

    1
    2
    
    let name: string = 'brein'; //单引号,双引号都可以
    const info: string = `My name is ${name}.`;
    
  • undefined 和 null

    1
    2
    
    let u: undefined = undefined;
    let n: null = null;
    
  • 数组

    有两种方式定义数组:一种是在数据类型后加上[];另一种是使用数组泛型:

    1
    2
    
    let arr1: number[] = [1, 2, 3];
    let arr2: Array<number> = [1, 2, 3];
    
  • 元组

    允许表示一个已知元素数量和类型的数组,各个元素的类型不必相同,赋值时顺序不可以变:

    1
    2
    
    let t1: [string, number] = ['hello', 10000]; //correct
    let t1: [string, number] = [10000, 'hello']; //error
    
  • 枚举

    enum类型是对javascript标准数据类型的补充。使用枚举类型可以为一组数据赋予友好的名字,让程序可读性大大提高

    1
    2
    3
    4
    5
    6
    7
    
    enum Color {
      Red = 1, //可以初始化第一个值,之后的值顺次+1;或者全部元素都可以手动赋值,不必连续
      Green,
      Blue,
    }
    let myColor: Color = Color.Blue;
    let colorName: string = Color[2];
    
  • any

    当想为那些在编程阶段不清楚类型的变量指定一个类型。这些值可能来自于动态的内容,比如来自用户的输入或第三方代码库,就可以使用any类型。在对现有代码进行改写时,any类型允许你在编译时可选择的包含、移除类型检查。并且,如果只知道部分类型,也可以用any

    1
    2
    3
    4
    5
    6
    7
    8
    
    let unknow: any = 4;
    unknow = 'string?';
    unknow = false;
    
    let list: any[] = [1, true, 'string'];
    list[1] = 11;
    //=======output=======
    false[(1, 11, 'string')];
    
  • void

    表示没有任何类型(与any相反),当一个函数没有任何返回值时,就可以使用void

    1
    2
    3
    
    function fn(): void {
      console.log('xxxxxxxxx');
    }
    
  • object

    主要可以表示原始类,可以使用像Object.create这样的方法。作为函数参数时,可以接收任何对象类型的数据。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    function fn1(object: object) {
      console.log(object);
    }
    fn1(new String('xxxxx'));
    fn1(new Number(123));
    fn1(new Boolean(true));
    fn1(new Date());
    //========output===========
    [String: 'xxxxx']
    [Number: 123]
    [Boolean: true]
    2021-07-22T17:12:11.525Z
    
  • 联合类型

    表示取值、返回值等可以是多个类型中的一种。

    1
    2
    3
    
    function toString(obj: number | string): string {
      return obj.toString();
    }
    

    这里说一句,联合类型个人认为不如使用泛型方便。

  • 类型断言

    通过类型断言可以告诉编译器,被断言的变量是可信的。类型断言只在编译时起作用,并不影响程序运行时。断言有两种形式:<尖括号>语法和as语法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    function getLength(obj: string | number): number {
      if ((<string>obj).length) {
        return (obj as string).length;
      } else {
        return obj.toString().length;
      }
    }
    console.log(getLength('hello world'));
    console.log(getLength(1000));
    //==============output===========
    11;
    4;
    
  • 类型推断

    TypeScript会在没有名确的指定类型的时候推测出一个类型。情况一:定义变量时赋值了,推断为值对应的类型;情况二:定义变量时没有赋值,推测为any,之后再赋值为其他类型,会被推断为最后赋值的那个类型。

    1
    2
    3
    4
    5
    6
    7
    8
    
    let testVal;
    testVal = 123;
    testVal = 'test string';
    console.log(testVal);
    console.log(typeof testVal);
    //==============output================
    test string
    string
    

接口

TypeScript的核心之一就是对值所具有的结构进行类型检查,我们使用接口来定义对象的类型。

接口就是对象的状态(属性)和行为(方法)的抽象。接口类型的对象的属性必须与接口完全一致。下面创建一个接口,并实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
interface person {
  readonly id: number; //只读属性
  name: string;
  age: number;
  gender?: string; //可选属性
  intro(): void;
}
const person1: person = {
  id: 1,
  name: 'brein',
  age: 18,
  gender: 'male',
  intro: function (): void {
    console.log(`My name is ${this.name}!!`);
    console.log(this);
  },
};
person1.intro();

接口可以继承其他接口,一个或者多个:

1
2
3
4
interface IProgramer extends person{
  xxxxxx
  ....
}

函数类型

接口还可以描述函数类型:

1
2
3
4
5
6
7
8
9
interface joinStr {
  (first: string, second: string): string;
}

const testJoin: joinStr = function (first: string, second: string): string {
  return first + second;
};

console.log(testJoin('hello,', 'world!!'));

类的类型

类的类型可以通过接口来进行设定,并且和C#一样,允许一个类继承多个接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(() => {
  interface ITalk {
    sayHello(): void;
  }

  interface IWork {
    workWithTools(): void;
  }

  class person implements ITalk, IWork {
    sayHello(): void {
      console.log('hello world!!');
    }
    workWithTools(): void {
      console.log('I can work with tools.');
    }
  }

  const p1 = new person();
  p1.sayHello();
  p1.workWithTools();
})();
//=============output==============
hello world!!
I can work with tools.

面向对象编程思想。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Student {
  name: string;
  age: number;
  gender: string;
  constructor(name: string, age: number, gender: string = 'male') {
    this.name = name;
    this.age = age;
    this.gender = gender;
  }
  sayHi() {
    console.log(
      `Hello everyone, my name is ${this.name}, my age is ${this.age}.`,
    );
  }
}

const stu = new Student('brein', 18);
stu.sayHi();
//============output=============
Hello everyone, my name is brein, my age is 18.

继承和多态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  class Person {
    name: string;
    age: number;
    gender: string;
    constructor(name: string, age: number, gender: string = 'male') {
      this.name = name;
      this.age = age;
      this.gender = gender;
    }
    sayHi() {
      console.log(
        `Hello everyone, my name is ${this.name}, my age is ${this.age}.`,
      );
    }
  }

  class Student extends Person {
    constructor(name: string, age: number, gender: string) {
      super(name, age, gender);
    }
    sayHi() {
      super.sayHi();
    }
  }

  const stu1 = new Student('brein', 18, 'male');
  stu1.sayHi();
//=======output========
Hello everyone, my name is brein, my age is 18.

多态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Teacher extends Person {
  constructor(name: string, age: number, gender: string) {
    super(name, age, gender);
  }
  sayHi() {
    super.sayHi();
  }
}
const tech1 = new Teacher('snn', 18, 'female');
function SayHi(obj: Person) {
  obj.sayHi();
}

SayHi(stu1);
SayHi(tech1);

const stu2: Person = new Student('xxxxx', 20, 'female');
const tech2: Person = new Student('yyyyy', 22, 'female');
stu2.sayHi();
tech2.sayHi();

修饰符

什么都不加,默认为 public。可用的其他修饰符有:private, protected。可以作用再属性,方法,构造函数上。还有一个readonly修饰符,只读,对于只读属性,除了声明时赋值外,只有构造函数中可以赋值。这些用法与C#都一致,就不实际演示了。

下面说一下,如果再构造函数的参数列表中使用readonly,那么表示该参数在类中也有一个同样的属性,在类中就不必特别写出。当然,一样是不可以被修改的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Person {
  constructor(
    readonly name: string,
    readonly age: number,
    readonly gender: string = 'male',
  ) {}
  sayHi() {
    console.log(
      `Hello everyone, my name is ${this.name}, my age is ${this.age}.`,
    );
  }
}

依次类推,如果用publicprivateprotected等进行修饰,同样会在类中生成对应的属性,根据修饰符的特性,赋予对应的访问权限。

存取器

TypeScript支持通过getter/setter来截取对象成员的访问,可以有效的控制对对象成员的访问。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Person {
  constructor(public firstname: string, public lastname: string) {}

  public get fullname(): string {
    return this.firstname + this.lastname;
  }

  public set fullname(v: string) {
    const fname = v.split('-');
    this.firstname = fname[0];
    this.lastname = fname[1];
  }
}
const p1 = new Person('zhang', 'san');
console.log(p1.fullname);
p1.fullname = 'li-si';
console.log(`firstname:${p1.firstname} lastname:${p1.lastname}`);
//==========output===============
zhangsan
firstname:li lastname:si

静态成员

一般的属性只有在类被实例化时,才会被实例化。而静态成员并不存在于类实例中,它是一直存在与内存中的。可以通过,类名.静态成员名的方式来访问。

抽象类

抽象列作为其他派生类的基类,不可以被实例化。不同于接口,抽象类可以包含成员的实现细节。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
abstract class Animal {
  constructor() {}
  sleep() {
    //可以包含具体实现
    console.log('sleeping...');
  }
  abstract cry(): void; //必须被子类实现
}
class Person extends Animal {
  constructor() {
    super();
  }
  cry() {
    console.log('i can cry.');
  }
}

const p1 = new Person();
p1.sleep();
p1.cry();

函数

封装一些重复使用的代码,在需要的时候可以直接调用。

前面都有涉及,本节略。

剩余参数

1
function show(str: string, ...args: string[]) {}

泛型

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function getArr<T>(value: T, count: number) {
  let arr: T[] = [];
  for (let index = 0; index < count; index++) {
    arr.push(value);
  }
  return arr;
}

console.log(getArr(1, 6));
console.log(getArr('brein', 6));

console.log(getArr<number>(1, 6));
console.log(getArr<string>('brein', 6));

多个泛型参数:

1
function getMsg<T, V>(value1: T, value2: V) {}

接口泛型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
interface IBaseCRUD<T> {
  data: Array<T>;
  add(t: T): T;
  get(index: number): T;
}

class userCRUD implements IBaseCRUD<string> {
  data: string[] = [];
  add(t: string): string {
    this.data.push(t);
    return t;
  }
  get(index: number): string {
    return this.data[index];
  }
}

泛型类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Queue<T> {
  private data: T[] = [];
  push = (item: T) => this.data.push(item);
  pop = (): T | undefined => this.data.shift();
}
const queue1 = new Queue<number>();
queue1.push(0);
queue1.push(1);

const queue2 = new Queue<string>();
queue2.push('xxx');
queue2.push('yyy');

console.log(queue1);
console.log(queue2);

泛型约束

1
2
3
4
5
6
7
8
interface ILength {
  length: number;
}

function getLength<T extends ILength>(params: T) {
  return params.length;
}
console.log(getLength<string>('xxxxxxx'));

其他

声明文件

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全,接口等功能。

声明语句:如果需要ts对新的语法进行检查,需要加载对应的类型说明代码,例如:

1
declare var jQuery: (selector: string) => any;

声明文件:把声明语句放到一个单独的文件(xxx.d.ts)中,ts会自动解析到项目中所有声明文件。

下载声明文件:以jquery为例,执行npm install @types/jquery --save-dev,就会把声明文件下载到node_modules下面的@types里面。

(全文完)