Obeta

TypeScript快速入门

用TS也一段时间了,由于我使用过Rust这类强类型语言,因此感觉TS确实很有未来市场,已经非常多的人开始是用了,可以在新项目中开始尝试使用(工期并不赶的前提下),总之就是早上早享受😏

声明:最好的入门文档是官文,我这篇只是一个快速入门的总结,需要高级用法建议 Google 相关的文章

TypeScript 是 JS 的超集,也就是 JS 有的它有,JS 没有的它也有(比如类,类型声明,接口,泛型等),方便了大型系统的开发和维护工作以及项目重构(JS 的弱项)

TS 最大的几个优点:

  • 静态类型:JS 是动态语言,这就导致了你代码没有执行到具体行你根本补能 100%确定这是否一定能跑,因此在大型项目中这是一种非常难以调试和查错的致命问题.而 TS 的静态类型解决了这个问题.
  • 编辑器支持:目前主流的编辑器或者相应等第三方扩展都提供了 TS 支持,可以实时查看代码提示.
  • 支持 ESNEXT:兼容目前所有的 JS 方法,不用担心代码无法使用.

话说最新的 ES10 给类增加了一个#符号,你可以把他看成关键字private,给类增加了一个真正意义上的私有属性,具体使用可以看TC39 草案,这里吐槽一下这个东西放到类里感觉很突兀,很多人也进行了吐槽和改进建议,但是目前还是没有一个好的方式,因为涉及到历史遗留问题.目前#还只是一个提案,没有第三方实现,但是如果最后进了标准那真是难受了...

安装

使用前需要先安装依赖包:

npm install tslint typescript -g

tslint --init

安装后创建一个test.ts文件,推荐使用 vscode 打开编辑,其它编辑器可能需要先设置一下.

可以编辑tslint.json文件定制自己项目的规则,比如:

{
	"defaultSeverity": "warning",
	"extends": ["tslint:recommended"],
	"linterOptions": {
		"exclude": ["node_modules/**"]
	},
	"rules": {
		"quotemark": [true, "single"],
		"interface-name": false,
		"ordered-imports": false,
		"object-literal-sort-keys": false,
		"no-consecutive-blank-lines": false,
		"eofline": false,
		"indent": [true, "tabs", 2],
		"trailing-comma": false,
		"no-console": [true, "info", "error"],
		"only-arrow-functions": false,
		"no-namespace": false,
		"no-shadowed-variable": false
	}
}

以下例子都可以在typescript-palyground里面使用,还可以看到输出的 es5 代码,非常直观.

类型

先看一下 TS 中有哪些可用的类型,类型声明是最基本的使用.

Number

不管是十进制还是二进制,都是number类型,这与其它强类型语言不太一样,并没有区分浮点还有精度问题.

let a: number = 10;
let b: number = 0xbeef;
let c: number = 0b0010;

String

字符串类型

let a: string = 'zxy';
let b: string = `welcome to ${a}`;

Boolean

还有常用的布尔类型

let a: boolean = true;
let b: boolean = false;

多种类型

有时候一个变量不一定只能为 string 类型,根据需求他可能是 number 类型,这时候可以使用:

let a: string | number = 'zzzz';
a = 10;

使用符号|来链接多个类型.

类型检查

基本类型使用typeof,引用类型比如类使用instanceof:

let a: string = 'zzz';
if (typeof a === 'string') {
	// someting
}

class Foo {
	name: string;

	constructor(name: string) {
		this.name = name;
	}
}
let foo: Foo = new Foo('zzz');
if (foo instanceof Foo) {
	// someting
}

instanceof检查的是实例foo中原型链中的__proto__是否有与Foo.prototype强相等的值,如果都没有就返回 false

prototype proto

类型转化

类型转化是一种非常常见的功能,有时候你会使用到

  1. 使用as关键字
let a: any = 'string';
let len: number = (a as string).length;
  1. 使用<>
let a: any = 'string';
let len: number = (<string>a).length;

上面两个是完全相同的功能,只是使用方式不一样

数组

数组声明使用type[]或者Array<type>

let arr: string[] = ['z', 'x', 'y'];

let array: Array<string> = ['a', 'b'];

创建了一个全是 string 类型的数组,如果需要多种类型:

let arr: (number | string)[] = [1, 'a'];
let array: Array<number | string> = [1, 'a'];

数组嵌套数组:

let arr: number[][] = [[1, 2, 3, 4], [5, 6, 7, 8]];

也可以使用 Python 中的Tuple元组类型:

let tuple: [string, number] = ['z', 1];

枚举

JS 缺少了枚举类型,不过 TS 中新增了,与其它语言类似:

enum Phone {
	RUN = 0,
	STOP = 1,
	PAUSE = 2,
}
// or
// enum Phone {
// 	RUN,
// 	STOP,
// 	PAUSE
// }

若不定义枚举中的值那么默认从 0 开始,也可以定义为 string 类型

自定义类型

大多数时候我们使用的都是自定义类型,比如 Object 和 Class,这时候我们可以使用type:

enum Level {
	HIGH,
	MIDDLE,
	LOW,
}
type People = {
	name: string;
	age: number;
	level: Level;
	city: string;
};

let people: People = {
	name: 'zzz',
	age: 23,
	level: Level.HIGH,
	city: 'chengdu',
};

还可以扩展(Union 联合类型),使用&:

type God = People & {
	fly: boolean,
	dead: boolean
}

interface BaseConfig { version: string; name: string; }
interface DynamicConfig { fromFile: string; }
interface StaticConfig { configuration: object; }
type Configuration = (StaticConfig | DynamicConfig) & BaseConfig;

const config: Configuration = {
  version: '1.0',
  name: 'myDynamicConfig',
  fromFile: './config.json'
}; // this is a DynamicConfig
const config: Configuration = {
  version: '1.0',
  name: 'myStaticConfig',
  configuration: { ... }
} // this is a StaticConfig

type有时候也可以用来做别名.

type MyString = string;

函数

可以定义函数的入参和返回参数:

function handle(a: number, b: number): number {
	return a * b;
}

// no return value
function log(str: string): void {
	console.log(`log value: ${str}`);
}

// optional value
function option(a: number, b?: number): number {
	return a * (b ? b : 1);
}
// default value
function add(a: number, b: number = 0): number {
	return a + b;
}

interface

interfacetype类型,不过使用的范围更广一些:

interface People {
	readonly id: string		// 只读属性(type中不可用)
	name: string
	age: number
	city?: string					// 可选属性(type中也可用)
}

let people: People = {
	id: 'sdfasdfassad'
	name: 'zzz',
	age: 12
}

interface也可以扩展,使用extends:

interface God extends People {
	fly: boolean;
	dead: boolean;
}

typeinterface可以互相扩展,更多关于他们不同点可以看这里,或者这里

范型

如果你使用过 Rust,那么你应该是很熟悉其中的使用了,想象一下下面这个函数:

function log(arg: any): any {
	console.log(arg);
	return arg;
}

虽然大多数时候问题不大,但是一眼看过去根本不知道这个函数做什么,因此直接使用any并不是很好,我们需要使用范型

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

使用范型可以让 TS 自动推断出来一些函数的返回值,而不需要手动指定,我们还可以指定 T 的范围:

interface People {
	name: string
}

funciton log<T extends People>(arg: T): T {
	return arg
}

const info = log({ name: 'zxy', age: 12 });

还可以给interface指定默认范型:

interface People<S = string, N = number> {
	name: S;
	age: N;
	address: S;
	readBookNumber: N;
}

const a: People<string, number> = {
	name: 'zzz',
	age: 10,
	address: 'zzzzz',
	readBookNumber: 23,
};
const b: People<number, number> = {
	name: 101,
	age: 10,
	address: 111111,
	readBookNumber: 23,
};

范型是一个高级话题,如果需要更多的信息,可以 Google 一下,这里不打算深入介绍,你也可以看看typescript-generics-and-overloads

函数重载

重载是列出给定函数的所有可能的输入/输出类型.重载没有函数体,也就是说只有声明,尽可能的覆盖所有输入/输出类型,并且支持过度重载.

function getArray(...args) {
	if (args.length === 1 && typeof args[0] === 'number') {
		return new Array(args[0]);
	} else if (args.length > 1) {
		return Array.from(args);
	}
}

getArray(5); // => [undefined x 5]
getArray('a', 'b', 'c'); // => ['a', 'b', 'c']

上面这个例子说明了有时候函数很复杂,会根据不同的入参决定返回相应的类型,因此这时候 TS 只能检测出来args类型为any[],返回值也是这个(你可以复制这段代码去 TS playground 里粘贴查看),为了明确函数入参和返回值类型,可以添加以下:

function getArray(...args: string[]): string[];
function getArray(args: number): undefined[];
function getArray(...args) {
	if (args.length === 1 && typeof args[0] === 'number') {
		return new Array(args[0]);
	} else if (args.length > 1) {
		return Array.from(args);
	}
}

getArray(5); // => [undefined x 5]
getArray('a', 'b', 'c'); // => ['a', 'b', 'c']

访问修饰符

通常类需要这类:

class A {
	private name: string;
	protected age: number;

	constructor(name: string, age: number) {
		this.name = name;
		this.age = age;
	}

	public getName() {
		return this.name;
	}
}

const a: A = new A('zyx', 100);
a.getName();

class B extends A {
	constructor(name: string, age: number) {
		super(name, age);
	}

	public getAge() {
		return this.age;
	}
}
const b: B = new B('zz', 200);
b.getAge();

映射类型

映射类型也就是(Mapped types),想象一下如下的使用场景:

// 有一个用户对象
interface User {
	name: string;
	age: number;
	city: number | string;
}

// 另一个文件中新增一个用户
const user: User = {}; /// 报错 error

这是因为添加用户字段的时候是一个个添加的,而不是一起添加,因此字段都是可选的,解决办法:

interface LocalUser {
	name?: string;
	age?: number;
	city?: number | string;
}

const user: LocalUser = {};

但是这种解决方案并不够好,也就是赘余了.这时候可以考虑使用inkeyof:

interface User {
	name: string;
	age: number;
	city: number | string;
}
type LocalUser = { [K in keyof User]?: User[K] };

这样LocalUser中的所有字段都变成可选的了.keyof是一个提取字段操作,可以想象成Object.keys():

type UserKey = keyof User; // "name" | "age" | "city"

还可以抽象成一个范型工具函数:

type Partial<T> = { [P in keyof T]?: T[P] };
type LocalUser = Partial<User>;

条件类型

  1. Exclude<T, U>: 一个内置类型,作用是从 T 中排除掉 U 类型

先来看看简单的条件语句

T extends U ? X : Y

就像是 JS 中的三元表达式一样,当 T 可以分配给 U 时,条件为真,值为 X.

type Other = Exclude<keyof User, 'city'>; // Other: "name"  | "age"
// 上面的keyof等于多个下面的操作
type Other =
	| Exclude<'name', 'city'>
	| Exclude<'city', 'city'>
	| Exclude<'age', 'city'>;

可以推断Exclude<T, U的官方定义是:

type Exclude<T, U> = T extends U ? never : T;

当然还有其它的,可以查看这里,条件类型是一种高级类型,一般来说项目不会复杂到这种程度,因此大概了解就可以了.

想了解高级类型最好的地方是官方文档Advanced Types .

这里有一个集成了各种 TS 的类型工具SimplyTyped,如果你在重复一些声明工作,那你可以想办法制造或者搜索对应的工具节省时间.

引用

个人随笔记录,内容不保证完全正确,若需要转载,请注明作者和出处.