# TypeScript基础二
# 类型操作
# 1、泛型
泛型可以理解为类型的参数,参数名可以自定义。目的是为了提高可重用性,以便支持新的数据类型。
- 泛型函数
// 示例一:泛型函数声明、调用、引用
function who<T>(arg: T): T {
return arg
}
// 传递所有参数,进行调用函数
const a = who<number>(10)
console.log(a)
// 不传类型参数,自动推断,进行调用函数
const b = who("string")
console.log(b);
// 直接函数声明引用函数
const c: <A>(arg:A) => A = who
// 函数调用签名引用函数
const d: { <A>(arg: A): A } = who
// 示例二:泛型支持设置默认值
type U<A, B> = A | B;
function fn<C = string, D = number>(p: U<C, D>):void{
console.log(typeof p);
}
fn(1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 泛型接口
// 示例一:上面的泛型函数介绍了两种引用函数的方式外,也可以通过接口实现函数的调用签名
interface IWho<B> {
(arg: B): B
}
const q: IWho<number> = who
// 示例二:泛型可以有多个
interface IPerson<T,K>{
name: T;
age: K;
}
const r: IPerson<string,number> = {
name: "yy",
age: 20
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 泛型类
举例如下,
class Drink<T>{
thing: T
constructor(what: T) {
this.thing = what
}
get what(): T {
return this.thing
}
set what(what: T) {
this.thing = what
}
}
class Cola {
// 类的静态属性不能使用泛型
static num: number = 0
add(): void {
console.log("It is the", ++Cola.num, " of cola, a drink.")
}
}
class Juice extends Cola {}
const colaDrink = new Drink(Cola)
console.log(colaDrink.thing)
colaDrink.what = Juice
console.log(colaDrink.thing)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 泛型约束
举例如下,
class BeeKeeper {
hasMask: boolean = false
}
class ZooKeeper {
nameTag: string = 'zoo'
}
class Biological {
numLegs: number = 2
}
class Bee extends Biological {
keeper: BeeKeeper = new BeeKeeper()
}
class Lion extends Biological {
keeper: ZooKeeper = new ZooKeeper()
}
// 通过extends约束泛型
function createInstance<T extends Biological>(animal: new () => T): T {
return new animal()
}
console.log("bee has mask:", createInstance(Bee).keeper.hasMask)
console.log("lion's nameTag is ", createInstance(Lion).keeper.nameTag)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2、keyof类型操作符
用于获取对象的键字面量的联合类型
interface IHuman{
name: string;
age: number;
male?: boolean;
[num: number]: any;
}
type TKey = keyof IHuman; // 相当于 "name"|"age"|"male"|"number"
let t:TKey = "name"
t = "male"
t = 1
2
3
4
5
6
7
8
9
10
# 3、typeof类型操作符
用于获取变量或属性的类型
class Greeter {
static defaultGreeting = "Hello, my friend";
greet() {
console.log(Greeter.defaultGreeting);
}
}
const greetMaker: typeof Greeter = Greeter;// typeof Greeter表示取Greeter类的类型
greetMaker.defaultGreeting = "Ladies and Gentlemen,good morning";
const greeter: Greeter = new greetMaker();
greeter.greet();
2
3
4
5
6
7
8
9
10
11
# 4、索引访问操作符T[K]
获取某一类型中某些属性的类型
type TPerson = { age: number; name: string; alive: boolean };
type One = TPerson["age"]; // number
type Two = TPerson["age" | "name"]; // number|string
type Three = TPerson[keyof TPerson];// number|string| boolean
2
3
4
# 5、条件类型
描述输入类型与输出类型之间的关系。形如SomeType extends OtherType ? TrueType : FalseType;
的类型格式
type L<T> = T extends string|number? T[]: T;
const o: L<number> = [1,2,3];
const s: L<boolean> = false;
2
3
结合类型推断关键字infer搭配条件类型,示例如下,
type W<T> = T extends {name: infer N, age: infer A}? [N, A]: null;
const x:W<{name:string,age:number}> = ['lisi',28];
const y:W<{name:'wangwu',age:30}> = ['wangwu',30];
const z:W<number> = null;
2
3
4
# 6、映射类型
可以用于属性或类型的处理,如属性只读、限制类型、或转换类型等等。
// 示例一
// 定义一个类型,其键可以是字符串或数值,值必须是字符串
type U = string|number;
type ITest = {
[k in U]: string;
}
const v:ITest = {
name: 'hello',
12: 'abc'
}
// 示例二
// 传入不同事件类型,生成对应事件处理函数类型。此处as关键字可理解为对键再作一次映射
type EventConfig<Events extends { kind: string }> = {
[E in Events as E["kind"]]: (event: E) => void;
}
type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };
type Config = EventConfig<SquareEvent | CircleEvent>
// type Config = {
// square: (event: SquareEvent) => void;
// circle: (event: CircleEvent) => void;
// }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 7、模板字面类型
可以认为是模板字符串在类型别名的应用
type LocaleId = `locale_id`;
type Lang = "en" | "ja" | "pt";
type LocaleMessageIDs = `${Lang}_${LocaleId}`;
2
3
# 类型别名
在前面知识点多多少少接触了,这里对它进行重新梳理。其用于自定义类型。类型别名与接口的区别有如下,
- 1、接口只能描述对象或函数,而类型别名能描述各种类型
- 2、接口支持重复声明,编译时自动声明合并,而类型别名不支持
- 3、在对象扩展上,接口使用extends实现,类型别名使用&实现
// 定义函数
type G = (p: number) => string;
const h:G = (a: number):string => `${a}`;
// 定义对象
type I = {
func: (p:string) => void,
}
const j:I = {
func: (s:string) => {
console.log(s);
}
}
// 类型别名可以是接口上某个属性的类型,通过索引获取
interface IType{
t: string;
}
type E = IType['t'];
const f:E = 'abc';
// 类型别名支持内部递归使用
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
};
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
# 工具类型
# 1、Partial
属性可选。
interface IPerson{
name: string;
age: number;
male?: boolean;
}
const q:Partial<IPerson> = {
name: 'zhangsan'
}
// 相当于
// const q:{
// name?: string|undefined;
// age?: number|undefined;
// male?: boolean|undefined;
// } = {
// name: 'zhangsan'
// }
type MPartial<T> = {[K in keyof T]?: T[K]|undefined}
const aq:MPartial<IPerson> = {
name: 'zhangsan'
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2、Required
属性必选。
interface IPerson{
name: string;
age: number;
male?: boolean;
}
const r:Required<IPerson> = {
name: 'lisi',
age: 20,
male: false,
}
// 相当于
// const r: {
// name: string;
// age: number;
// male: boolean;
// } = {
// name: 'lisi',
// age: 20,
// male: false,
// }
// 在修饰符readonly或?前加-,表示删除该修饰限制
type MRequired<T> = {[K in keyof T]-?: T[K]}
const ar:MRequired<IPerson> = {
name: 'lisi',
age: 20,
male: false,
}
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
# 3、ReadOnly
属性只读。
interface IPerson{
name: string;
age: number;
male?: boolean;
}
const s: Readonly<IPerson> = {
name: 'wagnwu',
age: 30,
}
type MReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
const as: MReadOnly<IPerson> = {
name: 'wagnwu',
age: 30,
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4、其他工具类型简介
Awaited<Type> // 获取异步操作返回值类型
Record<Keys, Type> // Keys作键集,Type是每个键值的类型
Pick<Type, Keys> //从Type取出指定Keys键集
Omit<Type, Keys> // 从Type剔除指定Keys键集
Exclude<UnionType, ExcludedMembers> // 从联合类型UnionType剔除被排除的成员ExcludedMembers
Extract<Type, Union> // 从Type和Union两种类型中取出共有属性
NonNullable<Type> // 从Type删除null或undefined类型
Parameters<Type> // 从Type取出参数类型
ConstructorParameters<Type> // 从Type取出构造函数参数类型数组
ReturnType<Type> // 从Type取出返回值类型
InstanceType<Type> // 从Type获取其实例类型
ThisParameterType<Type> // 从Type获取this参数类型
OmitThisParameter<Type> // 删除Type中this参数类型,再获取Type
ThisType<Type> // 仅作为上下文this类型的标记
Uppercase<StringType> // 将字符串转为大写
Lowercase<StringType> // 将字符串转为小写
Capitalize<StringType> // 将字符串首字母大写
Uncapitalize<StringType> // 将字符串首字母小写
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
30
31
32
33
34
35
传送门:工具类型官方文档链接 (opens new window)
# 声明合并
指编译器将针对同名的多处独立声明合并为一个声明。合并后的声明同时拥有原先所有声明的特性。
# 1、接口合并
接口的非函数成员应该是唯一的,若不唯一,必须保证相同类型,否则编译时报错。接口函数成员允许不唯一,需通过函数重载签名作兼容,调用时后声明的会优于先声明的。
interface ITest{
name: string;
func(n: number): void;
}
interface ITest{
// name: number;
age: number;
func(s: string): void;
}
const d:ITest = {
name: 'benben',
age: 10,
func: function(val: number|string){
console.log(val);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2、名称空间合并
namespace A{
export const a = 1
export const b = 2
}
namespace A{
export function funA(){
console.log(`Welcome to the namespace A`);
}
}
console.log(A); // { a: 1, b: 2, funA: [Function: funA] }
2
3
4
5
6
7
8
9
10
# 3、名称空间与函数、类、枚举的合并
当名称空间与函数、类或枚举同名时,彼此可以共享暴露的内容。
class Album {
label!: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel {}
}
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel {
export let suffix = "";
export let prefix = "Hello, ";
}
enum Color {
red = 1,
green = 2,
blue = 4,
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green;
} else if (colorName == "white") {
return Color.red + Color.green + Color.blue;
} else if (colorName == "magenta") {
return Color.red + Color.blue;
} else if (colorName == "cyan") {
return Color.green + Color.blue;
}
}
}
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
30
31
32
33
# 4、全局声明与模块内声明
// 全局声明,在整个项目可用
declare global {
interface Array<T> {
isUnique(value: unknown): boolean;
}
}
Array.prototype.isUnique = function (value: unknown): boolean {
for(let i = 0; i < this.length; i++){
if(this[i] === value){
return true;
}
}
return false;
};
// 模块内声明,声明模块内可用
declare module "./module" {
interface A {
a: number;
b: string;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 模块
当文件顶层出现import
或export
时,该文件被视为一个模块,否则视为全局的一段脚本。
# 1、导出export
// 声明导出。常量、变量、函数、类可以被导出,但接口、类型别名无法导出。
// 导出常量
export const a = 10;
// 导出变量
export let b = 2;
// 导出函数
export function func(){
return a + b
}
// 导出类
export class Print{
print(){
console.info("这是一条信息")
}
}
// 导出接口
export interface IPrint{
print(info:string):void
}
// 导出类型别名
export type Position = {
left: string,
right: string,
}
// 导出语句。指定要导出部分,亦可重命名。
let x:string, y, z;
export {
x as abc,
y as def,
z as hij
}
// 默认导出,相当于导出变量default
export default "Hello,Typescript"
// 从某模块导入再导出
export * as SomeModule from './xxx';
// 为了支持CommonJS和AMD的exports,提供了 export = XXX 语法。XXX表示一个模块的导出对象,可以是函数、类、枚举。使用 export = XXX 导出,必须使用 import XXX = require('XXX') 导入。当使用了该方式导出,其他导出方式均不生效。
enum Color{
RED,
BLUE,
YeLLOW,
};
export = Color;
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 2、导入import
// 部分导入
import {a, b, func as foo} from './module';
console.log(a, b, foo());
// 默认值导入
import str from './module';
console.log(str);
// 完整模块导入,包括默认导出值
import * as m from './module';
for(let k in m){
console.log("数据类型是",typeof k,"键为",k)
}
// 副作用导入,加载模块并执行
import './module';
// 支持CommonJS和AMD的导入方式。使用了该方式导入,则不支持部分导入,而默认导入和完整模块导入的值会跟着变化。
import Color = require('./module')
console.log(Color);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3、模块解析
指编译器在查找导入模块内容时所遵循的流程。可以在使用
tsc
编译时添加--traceResolution
选项,跟踪模块解析模块解析策略
分Node和Classic两种,默认是Node。若编译选项module指定为AMD、System或ES2015,则为Classic。以下面示例认识这两种策略。
// /root/src/folder/A.ts
// 相对导入模块
import * as B from "./moduleB";
// Classic策略查询路径:
// /root/src/folder/moduleB.ts
// /root/src/folder/moduleB.d.ts
// Node策略查询路径:
// /root/src/moduleB.ts
// /root/src/moduleB.tsx
// /root/src/moduleB.d.ts
// /root/src/moduleB/package.json (如果指定了"types"属性)
// /root/src/moduleB/index.ts
// /root/src/moduleB/index.tsx
// /root/src/moduleB/index.d.ts
// 非相对导入模块
import * as B from "moduleB";
// Classic策略查询路径:
// /root/src/folder/moduleB.ts
// /root/src/folder/moduleB.d.ts
// /root/src/moduleB.ts
// /root/src/moduleB.d.ts
// /root/moduleB.ts
// /root/moduleB.d.ts
// /moduleB.ts
// /moduleB.d.ts
// Node策略查询路径:
// /root/src/node_modules/moduleB.ts
// /root/src/node_modules/moduleB.tsx
// /root/src/node_modules/moduleB.d.ts
// /root/src/node_modules/moduleB/package.json (如果指定了"types"属性)
// /root/src/node_modules/moduleB/index.ts
// /root/src/node_modules/moduleB/index.tsx
// /root/src/node_modules/moduleB/index.d.ts
// /root/node_modules/moduleB.ts
// /root/node_modules/moduleB.tsx
// /root/node_modules/moduleB.d.ts
// /root/node_modules/moduleB/package.json (如果指定了"types"属性)
// /root/node_modules/moduleB/index.ts
// /root/node_modules/moduleB/index.tsx
// /root/node_modules/moduleB/index.d.ts
// /node_modules/moduleB.ts
// /node_modules/moduleB.tsx
// /node_modules/moduleB.d.ts
// /node_modules/moduleB/package.json (如果指定了"types"属性)
// /node_modules/moduleB/index.ts
// /node_modules/moduleB/index.tsx
// /node_modules/moduleB/index.d.ts
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
- 模块决策相关的选项参数说明
baseurl:告诉编译器到哪里去查找模块。非相对模块导入都会被当做相对于baseUrl;相对模块导入不会被设置的baseUrl所影响,因为它们总是相对于导入它们的文件。
paths:模块路径映射,简写形式方便导入相对模块
rootDirs:指定root列表,列表里的内容会在运行时被合并
示例如下,
"baseurl": ".",
"paths":{
"jquery": ["node_modules/jquery/dist/jquery"],
"*": ["*", "generated/*"] // "*"表示<moduleName>直接映射为<baseUrl>/<moduleName>,"generated/*"表示模块路径前是“generated”的,<moduleName>将映射为<baseUrl>/generated/<moduleName>
},
"rootDirs": ["src/views", "generated/templates/views"],// 这两个目录将在运行时自动合并在同一目录下
2
3
4
5
6
# 文件声明
在声明文件*.d.ts中作类型声明,可以是变量、函数、类、名称空间。若全局引进的第三方依赖如JQuery需类型增强,安装@type/jquery依赖即可。
# 三斜线指令
是包含单个XML标签的单行注释。注释的内容会做为编译器指令使用。仅可放在包含它的文件的最顶端,如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。
// 依赖声明
/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("https://www.typescriptlang.org");
// 包的依赖声明
/// <reference types="node" />
// 默认生成的AMD模块是匿名的,这个指令可以给amd模块设置名称
/// <amd-module name="amdName" />
2
3
4
5
6
7
8
9
10
# 名称空间
可以理解为在全局名称空间中命名的JavaScript对象。名称空间使用简单,与模块不同,一个名称空间可以跨多个文件定义,并可以通过--outFile
选项合并。
// Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
// LettersOnlyValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
// ZipCodeValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
// Test.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />
let strings = ["Hello", "98052", "101"];
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
for (let s of strings) {
for (let name in validators) {
console.log(
`"${s}" - ${
validators[name].isAcceptable(s) ? "matches" : "does not match"
} ${name}`
);
}
}
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
由于Test.ts通过三斜线指令引用了名称空间Validation多个文件,编译时会自动合并引入
tsc --outFile dist.js Test.ts
# 等价于 tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
2
# 类型兼容
- 基于结构的子类型
类型兼容是基于结构的子类型,它是一种只使用其成员来描述类型的方式。其规则是,如果x要兼容y,那么y至少具有与x相同的属性。
interface Person {
color: string;
}
class American {
// 必须存在,且类型必须一致
color: string = "white";
// 额外字段可选
name: string = "american";
age: number = 0;
}
let p: Person = new American();
console.log(p.color);
// 额外字段是无法调用的
console.log(p.name);// 报错:Property 'name' does not exist on type 'Person'.
2
3
4
5
6
7
8
9
10
11
12
13
14
- 函数兼容
要求参数列表中的必选参数类型按序保持一一对应,返回值类型也要一致。其中,参数列表可以具体展开为以下4点,
1、当源函数x的必选参数列表在目标函数y的必选参数列表都有,那么可以y=x是兼容的,否则不兼容。
2、源函数x与目标函数y的可选参数互不影响。
3、源函数x与目标函数y的剩余参数当作无限制的可选参数列表进行处理即可。
4、源函数有重载时,目标函数必须提供兼容所有重载的签名。
let x = (a: number, c?: string) => 0;
let y = (b: number, d: string, e?: boolean) => 0;
y = x; // OK
x = y; // 报错如下:
// Type '(b: number, d: string, e?: boolean) => number' is not assignable to type '(a: number, c?: string) => number'.
// Types of parameters 'd' and 'c' are incompatible.
// Type 'string | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
2
3
4
5
6
7
8
- 枚举兼容
数字枚举兼容数字类型,数字类型兼容数字枚举,但是不同数字枚举不兼容。
enum Status {
Ready,
Waiting,
}
enum Color {
Red,
Blue,
Green,
}
let status = Status.Ready;
status = 3;
status = Color.Red; // 报错:Type 'Color.Red' is not assignable to type 'Status'.
2
3
4
5
6
7
8
9
10
11
12
- 类兼容
比较两个类类型的对象时,只有实例的成员会被比较,即静态成员和构造函数不在比较范围内,但私有成员和受保护成员会被比较
- 泛型兼容
对于没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较。然后用结果类型进行比较
- 基本数据类型兼容
蓝勾表示支持类型兼容赋值如
const v:void = undefined
,红叉表示不支持类型兼容赋值。当配置项strictNullChecks为false时,绿勾生效。
# JSX
- 使用步骤
1、文件扩展名设置为.tsx;
2、启用jsx选项,提供preserve, react, react-native, react-jsx, react-jsxdev五个值。