typescript知识点

387 阅读12分钟

typescript

typescript简介

  1. 由微软开发的一款开源的编程语言
  2. 扩展了JavaScript的语法,是Javascript的超集,并遵循最新的es6、es5规范
  3. 有点像java、C#这样的面向对象语言,可以让js开发大型企业项目
  4. 最新的 Vue 、React 也可以集成 TypeScript
  5. 谷歌也在大力支持 Typescript 的推广,谷歌的 angular2.x+就是基于 Typescript 语法
  6. Nodejs框架 Nestjs、midway 中用的也是 TypeScript 语法

安装

npm i -g typescript
或
cnpm i -g typescript
或
yarn global add typescript

验证:

重启终端后,执行命令:

tsc -v

注:用yarn安装typescript后,可能无法直接使用tsc命令,需要配置环境变量才行。

自动编译

方式一: 命令行直接编译

// 在当前目录下编译生成js文件
tsc a.ts

// 指定生成目录
tsc a.ts --outDir './dist'

方式二: 通过tsconfig.json配置文件自动编译

自动生成tsconfig.json:

tsc --init

修改tsconfig.json配置参数:

{
    ...
    "outDir": "./dist"
    ...
}

配置vscode自动监听并编译:

点击vscode菜单中的 Terminal(快捷键shift + command + p) 
=> Run Task 
=> typescript 
=> 找到当前项目下的tsconfig.json 
=> 选择"tsc: watch: xxx/tsconfig.json

修改a.ts文件后,会自动watch并编译生成a.js文件

vscode自动编译ts语法-1.png

vscode自动编译ts语法-2.png

vscode自动编译ts语法-3.png

编译上下文

tsconfig.json配置

{
    "compilerOptions": {        
        /* 基本选项 */
        "target": "es5", // 生成的 ECMAScript 目标语言版本: ES3(default), ES5, ES2015, ES2016, ES2017, ESNEXT
        "module": "commonjs", // 指定生成的模块类型: amd, cmd, umd commonjs es2015, system
        "outDir": "./out", // 编译后的输出目录
        "rootDir": "./", // 用来控制输出的目录结构,比如把那一层目录放到outDir目录下
        "outFile": "./output.js", // 将输出文件合并为一个文件,只支持module是amd、system
        "removeComments": true, // 删除编译后的所有的注释
        "noEmit": true, // 不生成输出文件

        /* 严格类型校验选项 */
        "strict": true, // 是否启用严格模式
        "strictNullChecks": false, // 检查undefined、null是否赋值给了其他类型,默认true不允许赋值给其他类型

        
        /* 模块解析选项 */
        "moduleResolution": "node", // 模块解析策略,以什么顺序方式来加载引入的模块(见下链接)
        "baseUrl": "./", // 解析非相对模块的基地址
        "paths": {
            // 路径映射,相对于 baseUrl
            "@/*": ["./src/*"], // 就可以在代码里 import test from '@/utils';
            "jquery": [
                "node_modules/jquery/dist/jquery.slim.min.js"
            ]
        },

        
        "incremental": true, // 增量编译
        "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
        "diagnostics": true, // 打印编译信息
        "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在 AMD 模块中
        "lib": [], // TS 需要引用的库,即声明文件,es5 默认 "dom", "es5", "scripthost"
        "allowJs": true, // 允许编译 JS 文件(js、jsx)
        "checkJs": true, // 允许在 JS 文件中报错,通常与 allowJS 一起使用
        "rootDir": "./", // 指定输入文件目录(用于输出)
        "declaration": true, // 生成声明文件
        "declarationDir": "./d", // 声明文件的路径
        "emitDeclarationOnly": true, // 只生成声明文件
        "sourceMap": true, // 生成目标文件的 sourceMap
        "inlineSourceMap": true, // 生成目标文件的 inline sourceMap
        "declarationMap": true, // 生成声明文件的 sourceMap
        "typeRoots": [], // 声明文件目录,默认 node_modules/@types
        "types": [], // 声明文件包
        "removeComments": true, // 删除注释
        "noEmit": true, // 不输出文件
        "noEmitOnError": true, // 发生错误时不输出文件
        "noEmitHelpers": true, // 不生成 helper 函数,需额外安装 ts-helpers,目前也可以用 importHelpers 解决。
        "importHelpers": true, // 通过 tslib 引入 helper 函数,文件必须是模块
        "downlevelIteration": true, // 降级遍历器的实现(es3/5)
        "strict": true, // 开启所有严格的类型检查
        "alwaysStrict": false, // 在代码中注入 "use strict";
        "noImplicitAny": false, // 不允许隐式的 any 类型
        "strictNullChecks": false, // 不允许把 null、undefined 赋值给其他类型变量
        "strictFunctionTypes": false, // 不允许函数参数双向协变
        "strictPropertyInitialization": false, // 类的实例属性必须初始化
        "strictBindCallApply": false, // 严格的 bind/call/apply 检查
        "noImplicitThis": false, // 不允许 this 有隐式的 any 类型
        "noUnusedLocals": true, // 检查只声明,未使用的局部变量
        "noUnusedParameters": true, // 检查未使用的函数参数
        "noFallthroughCasesInSwitch": true, // 防止 switch 语句贯穿,分支没有 break
        "noImplicitReturns": true, // 每个分支都要有返回值
        "esModuleInterop": true, // 允许 export = 导出,由import from 导入
        "allowUmdGlobalAccess": true, // 允许在模块中访问 UMD 全局变量

        "rootDirs": [
            "src",
            "util"
        ], // 将多个目录放在一个虚拟目录下,用于运行时
        "listEmittedFiles": true, // 打印输出的文件
        "listFiles": true // 打印编译的文件(包括引用的声明文件)
    },
    
    "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        "src/global.tsx"
    ],
    "exclude": [
        "dist"
    ]
}

声明空间

包括:类型声明空间 和 变量声明空间

  1. 类型声明空间
class Foo {}      // 类可以作为变量赋值
interface Bar {}  // 接口类型不能作为变量赋值
type Tow {}       // 类型别名
  1. 变量声明空间
const a = Foo; // 类可以
const b = Bar; // 接口不能赋值

const c = 1;
const d: c;    // 变量不能作为类型声明

模块

包括:全局模块文件模块。文件模块存在本地作用域,变量只在当前文件生效,除非对其进行导出export。

只要某个ts文件中出现importexport则就被自动标记为文件模块,否则是全局模块。

全局模块中的全局变量,在多个全局模块里可以互相引用,编译时不会出错,但执行时会报错找不着变量。

文件模块用import/export对变量进行导入/导出操作。

示例:

全局模块 global.ts

const global1 = '这是全局模块里的变量';

文件模块 scope.ts

console.log(global1); // 引用全局模块里变量,编译时正常,执行时报错

import { scope2 } from '@/scope2';
import { scope2 as s } from '@/scope2'; // 重命名变量或类型

文件模块 scope2.ts

// es modle方式导出
// error TS2691: An import path cannot end with a '.ts' extension. Consider importing './scope.js' instead.
// 去掉文件后缀即可
export const scope2 = '这是文件模块里的变量';

数据类型

ts中的数据类型包括:

number      数字类型
string      字符串类型
boolean     布尔类型

undefined
null
any         任意类型
void
never

array       数组类型
tuple       元组类型
enum        枚举类型
readonley   只读类型
|           联合类型

object
Object
{}
  • 定义方式:定义变量时,在变量后边加冒号和类型,如let a: boolean = false;
  • 如果定义时未赋值,则结果为 undefined
let unknown1: number;     // undefined
let unknown2: undefined;     // undefined
let unknown3: number | undefined;     // undefined
  • 在变量被定义好类型后,不允许再被赋值为其他类型的数据。如:
let x: number = 2;
x = 'abc';      error TS2322: Type 'string' is not assignable to type 'number'

number、string、boolean

let num: number = 2;
// num = 'abc';      // error TS2322: Type 'string' is not assignable to type 'number'
let str: string = 'ab';
let bool: boolean = false;

any、unknown、undefined、null、void、never

let any1: any = 20;
any1 = 'abc';
// 如果未指定dom结点类型时会报错:error TS2339: Property 'style' does not exist on type 'Element'
let dom1: any = document.querySelector('.app'); // ok
dom1.style.background = '#ff0';

// 设置dom类型:
let dom2: any = document.querySelector('.app');
let dom3 = <HTMLElement>document.querySelector('.app');

unknown 与 any 区别:

  • 任何类型的值都可以赋值给anyany类型的值也都可以赋值给任何类型
  • 任何类型的值都可以赋值给unknown,但unknown只能赋值给unknownany
let s: number | string = 2;
let any1: any = [];
let un1: unknown = 3;

s = any1;
any1 = s;

un1 = s;
un1 = any1;
any1 = un1;
// s = un1; // error Type 'unknown' is not assignable to type 'number'

void:

  • 只有设置strictNullChecks: false时,void类型可赋值给nullundefined,否则不能直接赋值
  • 一般用于定义函数返回值类型。函数无返回值时可以定义成void,虽然默认返回undefined,但不能定义成undefined,否则会报错

never:

表示的是那些永不存在的值的类型。永远不存在值的情况有两种:

  • 函数抛出异常(程序执行终端,达不到终点)
  • 死循环(始终达不到终点)
function error(msg: string): never { // OK 
    throw new Error(msg);
}

function loop(): never { // OK 
    while (true) {};
}

注意:

  • nevernullundefined,也是任何类型的子类型,也可以赋值给任何类型
  • 除了never本身之外,任何类型都不能赋值给never类型(没有子类型)

数组

let ary1: string[] = ['a', 'b'];
let ary2: Array<any> = [2, 'b'];

// 使用接口定义数组成员:
interface IAryObj {
    id: number;
    name: string;
}
let ary3: Array<IAryObj> = [
    {id: 1, name: 'zhang'},
];
let ary4: IAryObj[] = [
    {id: 1, name: 'zhang'},
]

元组

数组中定义不同类型的成员

let tuple1: [number, boolean, any] = [3, false, {a: 2}]; // 每项单独指定类型
let tuple2: Array<string> = ['a', 'b']; // 指定所有项都是string
let tuple3: Array<any> = [5, 'b'];

解构赋值:
let info: [number, string] = [3, 'abc'];

可选类型:(如:一维、二维、三维坐标)
let pixel: [number, number?, number?] = [2, 3];
let info: [number?, string] = [3, 'abc']; // error  可选类型后面不能有必选类型

枚举

enum Color {
    red,            // 0
    green = 6,      // 6
    blue            // 7
};
// 如果没指定值,则默认从索引0开始,后一个是前一个值加1
let color: Color = Color.blue;
console.log(Color.red, Color.green, Color.blue); // 0 6 7

enum Status {
    success = 0,
    error = -1,
    unLogin = 'unlogin'
};
let state: Status = Status.unLogin; // unlogin

联合

let s: number | string = 2;
let ary1: (number | string)[] = ['a', 3];
let ary2: Array<number | string> = ['a', 3];

readonly只读

let on: readonly[number, boolean] = [3, false];
// on[0] = 2; // error 只读

object、Object、{}

js 中的原始类型包括:stringbooleannumberbigintsymbolnull 和 undefined

  • 小 object 代表的是所有非原始类型,不能把 number、string、boolean、symbol等 原始类型赋值给 object。在严格模式下,null 和 undefined 类型也不能赋给 object
  • 大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object。同样,在严格模式下,null 和 undefined 类型也不能赋给 Object
  • 大 Object 包含原始类型,小 object 仅包含非原始类型,所以大 Object 似乎是小 object 的父类型。实际上,大 Object 不仅是小 object 的父类型,同时也是小 object 的子类型
  • Object 即是 object 的父类,也是 object 的子类,但两者并不等价官方说用小 object
  • {} 空对象类型和大 Object 一样,也是表示原始类型和非原始类型的集合,并且在严格模式下,null 和 undefined 也不能赋给 {}
let o1: object;
// obj = 1;    // error 不能赋值原始类型
// obj = undefined; // error 严格模式下不允许
// obj = null; // error 严格模式下不允许
o1 = {};   // ok
o1 = [];   // ok

let o2: Object;
o2 = 1;
// o2 = undefined;   // error 严格模式下不允许
// o2 = null;        // error 严格模式下不允许
o2 = {};

// Object 即是 object 的父类,也是 object 的子类
type x = object extends Object ? true : false;    // true
type y = Object extends object ? true : false;    // true
o1 = o2;
o2 = o1;

let o3: {};     // {} 和 大 Object 一样
// o3 = null;  // error 严格模式下不允许

部分参考:2021 typescript史上最强学习入门文章(2w字)

函数

// 默认参数
function func2(id: number, name: string = 'zhang'): string {
    return name;
}

// 匿名函数
// 可选参数:参数后边加?,可选参数后边不能出现必选参数
let func4 = function (id?: number, name?: string) {
    return name;
}

// 剩余参数
let func = (...ret: string[]): string => {
    return ret.join('-');
}

// 函数重载
// 为同一个函数提供多个函数类型定义(和java稍微不一样)
function getUser(name: string): string;
function getUser(name: string, age: number): string;
function getUser(name: any, age?: any): any {
    if (age) {
        return name + ' ' + age;
    } else {
        return name;
    }
}

// 使用接口定义函数类型
interface IFc7 {
    (id: number, name: string): object;
}
let fc7: IFc7 = (id: number, name: string) => ({id, name});
let fc8: IFc7 = function(id: number, name: string): object {
    return {id, name};
}

  • 继承:extends
  • 初始化父类构造方法:super()
  • 修饰符
    • public:在当前类内部、子类内部、类外部均可访问
    • protected:在当前类内部、子类内部中可访问
    • private:在当前类内部可访问

如果成员属性、成员方法前面没有修饰符,则默认是共有public

类的定义

class Father {
    name: string;   // ts中定义成员属性,需要先声明其类型。es6中不需要(默认共有属性,省略public关键字)
    public sex: string; // 共有属性

    constructor(name: string, sex?: string) {    // 实例化类时触发
        this.name = name;   // 当前类内部访问公有属性
        this.sex = 'male';
    }
    getName(): void {     // 默认共有方法,省略public关键字
        console.log(this.name);
    }
    public sleep() {
        this.smile();   // 当前类内部访问受保护方法
        this.drive();   // 当前类内部访问私有方法
        console.log(`${this.name} is sleeping`);
    }
    protected smile() {
        console.log('can smile');
    }
    private drive() {
        console.log('can drive');
    }
}

let f = new Father('zhangsan');
f.getName();
f.name;     // 在类外部访问public共有属性
// f.smile();  // error 外部无法访问受保护成员
// f.drive(); // error 类外部不能访问私有方法

类的继承

  • 通过extends、super实现继承
class Child extends Father {
    constructor(name: string) {     // 貌似子类不写构造方法也ok?
        super(name);    // 初始化父类构造方法
    }
    // 子类有和父类同名成员方法,优先调用子类本身的
    getName(): void {
        this.smile();   // 子类内部也可以访问受保护成员
        // this.drive();   // error 子类内部无法访问父类私有方法
        console.log(`I am ${this.name}`);
    }

}
console.log('----------------');
let c = new Child('Child');
c.getName();    // 优先访问自身方法,没有时再访问父类的
c.name;     // 类外部访问公有属性
c.sleep();  // 子类访问父类公有方法
// c.smile();  // error 外部无法访问受保护成员
// c.drive(); // error 类外部不能访问私有方法

静态属性和方法

  • 通过static定义,一般不常用
  • es6中的静态属性和方法,只能通过类、或类内部的静态方法去调用,不能通过this调用

es5中定义静态属性和静态方法

function AbsFunc () {
    this.name = 'abs';  // 实例属性
    this.go = function () {}  // 实例方法
}
AbsFunc.sleep = function () {}; // 静态方法

let abs = new AbsFunc();
abs.go();       // 实例属性和方法,需要通过实例对象去调用
AbsFunc.sleep(); // 静态属性和方法,需要通过类名去调用

// 例如jquery中:
function $ () {
    return new Base(ele);   // 返回Base实例对象
}
function Base(ele) {
    this.css = function () {}
}
$.get = function() {}   // 静态方法

$('.app').css();    // 实例方法:通过实例对象调用,$('.app')返回实例对象
$.get(url);         // 静态方法:通过类名调用,$返回静态类

ts中的静态属性和方法:

class StaticCls {
    name: string;
    static sex: string = 'male'; // 静态属性
    constructor(name: string) {
        this.name = name;
    }
    static getSex(sex: string) {
        StaticCls.sex = sex;
        console.log('static class:', StaticCls.sex); // 不能通过this.sex调用
    }
}

StaticCls.getSex('female');  // female

多态

  • 属于继承
  • 父类定义完方法后不去实现,由继承的子类去实现,每个子类有不同的表现
class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    eat() {     // 定义完方法后不去实现

    }
}

class Boy extends Person {
    constructor(name: string) {
        super(name);
    }
    eat() {
        return this.name + ' eat';
    }
}

抽象类和抽象方法

  • abstract关键字定义抽象类和抽象方法,抽象方法只能放在抽象类里面
  • 抽象类不能直接被实例化,只是提供其他类继承的基类
  • 抽象类中的抽象方法不包含具体的实现,必须在派生类中实现
abstract class Person2 {
    constructor(name: string) {}
    abstract eat(): any;
}
// let per2 = new Person2('li');    // 抽象类不能被实例化

class Boy2 extends Person2 {
    constructor(name: string) {
        super(name);
    }
    eat(): any {        // 必须实现父类中的抽象方法
        return 'eating';
    }
}
let b2 = new Boy2('boy');
b2.eat();   // eating

接口

interface IBook {
    id: number;
    type?: string;  // 可选属性
}

let book: IBook = {id: 1}
function get(param: IBook) {}

接口是对行为的抽象,定义标准,起到一种限制和规范作用,并不关心具体的实现和内部状态,而具体的实现是由去完成。

ts与node

es6(es2015)中引入模块化机制,node基本支持es6中的所有语法,但不支持模块化机制,即import/export

如何让node运行es6 module呢?

  1. es6 module 改成commonjs
  2. rollup转换
  3. babel-node直接允许es6

1. es6 module 改成commonjs

scope.ts文件:

//commonjs方式导出
// error TS2580: Cannot find name 'exports',加上declare声明即可
declare const exports: any;
exports.scope1 = '这是文件模块(本地作用域)';

或

declare const module: any;
module.exports = {
    scope1: '这是文件模块(本地作用域)'
};

index.ts文件:

// error TS2580: Cannot find name 'require',加上declare声明即可
declare const require: any; 
const {scope1} = require('./scope');