一篇文章学会 TypeScript (内部分享标题:TypeScript 基础)

这篇文章是我在公司前端小组内部的演讲分享稿,目的是教会大家使用 TypeScript,
这篇文章虽然标着基础,但我指的基础是学完后就能够胜任 TypeScript 的开发工作。
从我分享完的效果来看,大家都学会了,效果还是好的。我把这篇分享稿发布出来,
希望能对大家的 TypeScript 学习有所帮助。

1、什么是 TypeScript?

TypeScript 是由微软发布的一款开源的编程语言。它是 JavaScript 的超集(兼容 JavaScript 代码),其代码必须经过编译后,方可在 JavaScript 环境中运行,核心功能是类型系统可以提前使用 ES 的新特性

接下来来看一段代码示例:

// TypeScript 语法function add(x: number, y: number): number {    const res: number = x + y;    return res;}// 与 C 语言比较int add(int x, int y) {    int res = x + y;    return res;}

当类型不对的时候,IDE 会提示错误:

编译后:

// JavaScript 语法function add(x, y) {    const res = x + y;    return res;}

联想:大致可以把它看成是加了类型系统的 Babel。

问题:为什么要学习 TypeScript?

  1. 类型系统可以在代码编译阶段就可以发现潜在的变量类型问题,甚至在开发阶段,IDE 就可以帮助我们找到这些潜在的问题,避免了项目在上线后才发现变量类型不对的错误。
  2. TypeScript 是前端开发的趋势,有些特性还可能会成为 ES 的新标准,Vue 3.0 也会采用 TypeScript 作为开发语言,当你想看源码的时候不至于看不懂,跟着趋势走肯定是没错的。
  3. 减少类型校验等无用的代码书写。
  4. 可以提前享受 ES 新特性带来的便利。
  5. 向 qian 看,qian 途。

2、快速上手,三步走

第一步:全局安装 TypeScript

$ yarn global add typescript// 测试是否安装成功$ tsc -vVersion 3.4.5

第二步:创建 .ts 文件

// add.tsfunction add(x: number, y: number): number {    const res: number = x + y;    return res;}export default add;

第三步:编译

$ tsc add.ts

// add.jsfunction add(x, y) {    const res = x + y;    return res;}export default add;

文件监听,实时编译

适用于边开发边看结果的情况:

$ tsc -w add.tsStarting compilation in watch mode...Watching for file changes.

3、TypeScript 的基本类型

// 布尔值let isDone: boolean = false;// 数值let age: number = 12;// 字符串let name: string = 'Jay';// 数组let list: number[] = [1, 2, 3];let list: Array<number> = [1, 2, 3];// 对象let mokey: object = {name: 'wu kong'};// 空值,表示没有返回值function print(): void {}// 任意值let goods: any = {};let goods: any = 2019;// 未指定类型,视为任意值let goods;// 类型推断let name = 'Jay';name = 123; // 报错// 联合类型let name: string | number;name = 'Jay';name = 123;

4、类和接口

类是对属性和方法的封装

类的示例:

// 跟 JavaScript 的类相似,多了访问控制等关键词class Monkey {    public height: number;    private age: number = 12;    public static mkName: string = 'kinkong';    private static action: string = 'jump';        constructor(height: number) {        this.height = height;    }    public getAge(): number {        return this.age;    }}const monkey = new Monkey(120);monkey.getAge();const height = monkey.height;const age = monkey.age; // 报错const mkName = Monkey.mkName;const action = Monkey.action; // 报错
类的继承
class Animal {    move(distanceInMeters: number = 0) {        console.log(`Animal moved ${distanceInMeters}m.`);    }}class Dog extends Animal {    bark() {        console.log('Woof! Woof!');    }}const dog = new Dog();dog.bark();dog.move(10);
protected、private、readonly 和 super
  • protected 和 private 都不能从外部访问
  • protected 可以被继承,private 不可以
class Person {    protected name: string;    private age: number;    constructor(name: string, age: number) {         this.name = name;         this.age = age;    }}class Employee extends Person {    private readonly initial: string = 'abc';    private readonly department: string;    constructor(name: string, department: string) {        super(name, 1);        this.department = department;    }    public getElevatorPitch() {        return `Hello, my name is ${this.name} and I work in ${this.department}.`;    }    public getAge() {        return this.age;    }}let howard = new Employee("Howard", "Sales");console.log(howard.getElevatorPitch());console.log(howard.name); // error
let passcode = "secret passcode";class Employee {    private _fullName: string;    get fullName(): string {        return this._fullName;    }    set fullName(newName: string) {        if (passcode && passcode == "secret passcode") {            this._fullName = newName;        }        else {            console.log("Error: Unauthorized update of employee!");        }    }}let employee = new Employee();employee.fullName = "Bob Smith";if (employee.fullName) {    console.log(employee.fullName);}
类可以用于接口继承
class Point {    x: number;    y: number;}interface Point3d extends Point {    z: number;}let point3d: Point3d = {x: 1, y: 2, z: 3};
接口

接口是用来定义规范的,常常用作类型说明。
接口的示例:

// 定义规范,限定类型interface Point {    x: number;    y: number;    // 可选属性    z?: number;}let pt: Ponit = {x: 1, y: 1, z: 1};function setPoint(point: Point): void {}setPoint({x: 1, y: 1});// 实现接口class point implements Point {    x: number;    y: number;    constructor(x: number, y: number) {        this.x = x;        this.y = y;    }    public getPoint(): object {        return {            x: this.x,            y: this.y,        }    }}const p = new point(3, 4);p.getPoint();
接口继承接口
interface Shape {    color: string;}interface Square extends Shape {    sideLength: number;}
初始化参数规范
interface ClockConstructor {    new (hour: number, minute: number);}class Clock implements ClockConstructor {    currentTime: Date;    constructor(h: number, m: number) { }}

5、type、 namespace 和泛型

type 定义新类型

当 TypeScript 提供的类型不够用时,可以用来自定义类型,供自己使用。

// type 定义新类型,相当于类型别名type myType = string | number | boolean// 使用:const foo: myType = 'foo'// type 定义函数类型type hello = (msg: string) => void// type 对象类型type WebSite = {    url: string;    title: number;}

namespace 命名空间

命名空间主要有两个方面的用途:

  1. 组织代码。组织一些具有内在联系的特性和对象,能够使代码更加清晰。
  2. 避免名称冲突。
namespace Validator {    export interface StringValidator {        isAcceptable(s: string): boolean;    }    const lettersRegexp = /^[A-Za-z]+$/;    const numberRegexp = /^[0-9]+$/;// 当声明一个命名空间的时候,所有实体部分默认是私有的,可以使用 export 关键字导出公共部分。    export class LettersOnlyValidator implements StringValidator {        isAcceptable(s: string) {            return lettersRegexp.test(s);        }    }    export class ZipCodeValidator implements StringValidator {        isAcceptable(s: string) {            return s.length === 5 && numberRegexp.test(s);        }    }}new Validator.LettersOnlyValidator();interface MyClassMethodOptions {    name?: string;    age?: number;}namespace MyClass {  export interface MyClassMethodOptions {    width?: number;    height?: number;  }}
使用外部定义的 namespace
/// <reference path="validate.ts" />const v = new Validator.LettersOnlyValidator();

泛型

泛型允许在强类型程序设计语言中编写代码时,使用一些以后才指定的类型,在实际调用时才去指定泛型的类型。

举例:

// 模拟服务,提供不同的数据。这里模拟了一个字符串和一个数值const service = {    getStringValue: function() {        return "a string value";    },    getNumberValue: function() {        return 20;    }};// 处理数据的中间件function middleware(value: string): string {    return value;}// 没问题middleware(service.getStringValue());// 报错:参数类型不对middleware(service.getNumberValue());// 设成 any 行不行?function middleware(value: any): any// 多写几个 middleware 行不行?function middleware1(value: string): string { ... }function middleware2(value: number): number { ... }// 改为泛型function middleware<T>(value: T): T {    return value;}// 使用middleware<string>(service.getStringValue());middleware<number>(service.getNumberValue());

更多内容查看文章:泛型

6、xxx.d.ts声明文件

如果把 add.js 发布成一个 npm 包,别人在使用的时候 IDE 的提示往往都不太友好:

那么如何能让 IDE 给 add 方法加上类型提示和参数提示呢?

TypeScript 提供了 xxx.d.ts 文件来给 IDE 使用。

xxx.d.ts 叫做声明文件,一般用于类型提示和模块补充,可以自动生成,也可以手动编写,一般情况下是不用我们手动编写的。

// 加上 -d 参数,就会生成 add.d.ts 文件$ tsc -d add.ts

// add.d.tsdeclare function add(x: number, y: number): number;export default add;

declare 关键字
用于声明你需要的变量、函数、类等,声明以后 IDE 就可以根据它来进行类型提示。

如果有 add.d.ts 文件,那么它就承担了类型提示的任务:

同时也相当于 add 方法的接口文档:

手动创建声明文件

有以下两个场景会去手动创建这个文件:

  1. 给未使用 TypeScript 的 js 模块编写声明文件,可以当做交接文档用;使用别人没有写声明文件的 js 库。
  2. 模块补充。
// 定义模块补充的语法declare module 'filePath' {   }

module-augmentation

使用场景:vue-shim.d.ts 文件,为了让 TypeScript 识别 .vue 文件。

// 文件内容declare module "*.vue" {    import Vue from "vue";    export default Vue;}

增强类型以配合插件使用

7、配置文件

冗长的参数

指定输出目录:

$ tsc -d --strict -m ESNext --outDir lib index.ts

tsc 的所有参数可以通过执行:tsc -h来查看。

使用 tsconfig.json 配置文件

避免书写冗长的命令,丰富的配置项。

生成 tsconfig.json 文件:

$ tsc --init

常用配置项解读:

{  "compilerOptions": {    "target": "ES5", /* target用于指定编译之后的版本目标 version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */    "module": "ESNext", /* 用来指定要使用的模块标准: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */    "declaration": true,  /* 生成对应的 '.d.ts' 声明文件 */    "outDir": "./lib", /* 指定编译输出目录 */    "esModuleInterop": true, /* 通过为导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性 */    "experimentalDecorators": true, /* 启用 ES7 装饰器语法 */  }}

查看更多配置项

配置后,执行以下命令,就会读取配置文件:

$ tsc add.ts

8、使用 TypeScript 开发 web 应用

上述的方法适用于开发命令行工具,比如 @xiyun/cli,开发 web 应用还是不太方便。

如果想要方便地开发 web 应用,比如:开启前端服务、模块热加载等,就需要配合构建工具使用。

配合 webpack 开发 web 应用

点击查看配合 webpack 开发 web 应用源码

配合 parcel,零配置开发 web 应用

parcel:极速零配置 Web 应用打包工具。

联想:可以看成是已经配置好了的 webpack。

全局安装 parcel 工具:

$ yarn global add parcel-bundler

运行:

$ parcel index.html

它就会帮你生成 package.json,自动安装 typeScript,启动好开发服务,并支持热加载。

9、在 Vue 和 React 项目中使用

让 Vue 支持 TypeScript

最方便快捷:vue create my-app,选择 TypeScript。

如果要改造现有项目:

  1. 增加 dev 依赖包:
"devDependencies": {    "@vue/cli-plugin-typescript": "^3.8.0",    "typescript": "^3.4.3",  }
  1. 增加 tsconfig.json 配置
  2. 更改文件后缀:main.js --> main.ts
  3. 组件中指定 lang:
<script lang="ts"></script>
  1. 在 src 目录下加上:vue-shim.d.ts 文件,为了让 TypeScript 识别 .vue 文件。
// 文件内容declare module "*.vue" {    import Vue from "vue";    export default Vue;}

Vue 支持 TypeScript 的配置参考

另外一种语法

如果想要用这种语法来写 Vue 组件:

import { Component, Prop, Vue } from 'vue-property-decorator';@Componentexport default class HelloWorld extends Vue {  @Prop() private msg!: string;}

那么需要加上这两个依赖:

"dependencies": {    // 官网出的ts支持包    "vue-class-component": "^7.0.2",    // 对ts支持包更好封装的装饰器    "vue-property-decorator": "^8.1.0"  },

Vue 官方对 TypeScript 的说明

创建支持 TypeScript 的 React 项目

创建应用时加上--typescript参数即可:

$ create-react-app my-app --typescript

参考资料

TypeScript 官方文档
TypeScript Declare Keyword
Vue 对 模块补充的说明