TypeScript
TypeScript 的目标是成为 JavaScript 程序的静态类型检查器——换句话说,是一个在代码运行之前运行的工具(静态)并确保程序的类型正确(类型检查)
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程
TypeScript 是一种非常受欢迎的 JavaScript 语言扩展。它在现有的 JavaScript 语法之上加入了一层类型层,而这一层即使被删除,也丝毫不会影响运行时的原有表现。许多人认为 TypeScript “只是一个编译器”,但更好的理解其实是把 TypeScript 看作两个独立的系统:编译器(即处理语法的部分)和语言工具(即处理与编辑器集成的部分)。通过独立看待这两个系统,就可以得到能够解释我们之前所做决策的两个重要视角
安装TypeScript :npm i typescript
(npm i typescript -g
全局安装)
编译 ts文件 :tsc test.ts
编译成功会生成相应的js文件
解决TS和JS冲突 :tsc --init
生成配置文件
//类型检查
/* Type Checking */
"strict": true, //true 严格模式
自动编译 :tsc --watch
发出错误 :tsc -noEmitOnError test.ts
当有错误时不会编译成js文件
let test:string //变量名:类型
//在配置文件中
/* Language and Environment */
"target": "es2016", //通过修改该配置,改变编译成的js文件符合的ECMAScript标准
/* Type Checking */
"strict": true,
// "noImplicitAny": true, //true将对类型隐式推断为,当任何变量发出错误时都应用 any 类型
// "strictNullChecks": true, //null和undefined不能分配给任意类型
//第一种
let arr: number[] = [1, 2, 3] //指定数组中的元素均为number类型,此语法适用于任何类型
//第二种
let arr2: Array<number> = [1, 2, 3]
当你使用 const , var , 或声明变量时 let ,可以选择添加类型注释来显式指定变量的类型
let test: string = "aaa";
在大多数情况下,TypeScript 会尝试自动推断代码中的类型
参数类型注释:声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型。参数类型注释位于参数名称之后
返回类型注释:添加返回类型注释。返回类型注释出现在参数列表之后
匿名函数:当一个函数出现在 TypeScript 可以确定它将如何被调用的地方时,该函数的参数会自动指定类型
function test(name: string):void {
console.log("Hello, " + "!!");
}
定义对象类型,列出其属性及其类型
可选属性:指定其部分或全部属性是可选的。在属性名称后添加一个?
// 参数的类型是对象类型
function printName(obj: { first: string; last?: string }) { //这里可以使用','或者';'分隔,
// ...
console.log(obj.last?.toUpperCase()); //当需要使用可选属性的属性时,可以采用xxx?.xxx的形式,自动检查其是否为undefined
}
// 两种传递参数都可以
printName({ first: "Felix" });
printName({ first: "Felix", last: "Lu" })
string|number|...
表示或的关系,即变量可以是这几种类型中的某一种
//语法:type 别名 = 值
type Point = { //对象类型别名
x: number;
y: number;
};
type ID = number | string //联合类型别名
一个接口声明是另一种方式来命名对象类型
interface Point {
x: number;
y: number;
}
类型别名和接口之间的差异
类型别名和接口非常相似,在很多情况下可以相互替换
关键区别在于扩展新类型的方式不同
// 扩展接口
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
//通过交叉点扩展类型别名
type Animal = {
name: string
}
type Bear = Animal & {
honsy: boolean
}
// 向现有接口添加新字段
interface MyWindow {
title: string
}
interface MyWindow {
count: number
}
const w: MyWindow = {
title: 'hello ts',
count: 100
}
// 类型创建后不可更改
用于指定具体的类型
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas"); //.tsx 文件中不推荐使用
TypeScript 只允许类型断言转换为更具体或不太具体的类型版本
let x: "hello" = "hello" //相当于 const x = 'hello'
let y: "aa" | "bb" | "cc" // y只能为三者之一
let z: -1 | 0 | 1 //z只能为 -1 | 0 | 1
interface Options { width: number; }
let m: Options | 'auto' //m只能为 Options | 'auto'
let b1: true = true
let b2: false = false
非空断言运算符( ! 后缀)
function liveDangerously(x?: number | null) {
// 正确
console.log(x!.toFixed())
}
枚举是 TypeScript 添加到 JavaScript 的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。
enum Direction {
Up = 1,
Down,
Left,
Right,
}
从 ES2020 开始,JavaScript 中有一个用于非常大的整数的原语 BigInt
// 通过bigint函数创建bigint
const oneHundred: bigint = BigInt(100);
// 通过文本语法创建BigInt
const anotherHundred: bigint = 100n;
JavaScript 中有一个原语 Symbol() ,用于通过函数创建全局唯一引用
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
// 这里的代码不可能执行
}
JavaScript 支持一个 typeof 运算符,它可以提供有关我们在运行时拥有的值类型的非常基本的信息。TypeScript 期望它返回一组特定的字符串:
在 TypeScript 中,检查 typeof 的返回值是一种类型保护
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
} else {
// 做点事
}
}
0,NaN,"" (空字符串),0n ( bigint 零的版本),null,undefined
所有值强制都转换为 false ,其他值被强制转化为 true
可以在 Boolean 函数中运行值获得 boolean ,或使用较短的双布尔否定将值强制转换为 boolean (后者的优点是 TypeScript 推断出一个狭窄的文字布尔类型 true ,而将第一个推断为 boolean 类型)
// 这两个结果都返回 true
Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true, value: true
定义一个函数,其返回类型是一个类型谓词
type Fish = {
name: string
swim: () => void
}
type Bird = {
name: string
fly: () => void
}
function isFish(pet: Fish | Bird): pet is Fish { //谓词的形式是 parameterName is Type
return (pet as Fish).swim !== undefined
}
function getSmallPet(): Fish | Bird {
let fish: Fish = {
name: 'gold fish',
swim: () => { }
}
let bird: Bird = {
name: 'sparrow',
fly: () => { }
}
return true ? bird : fish
}
// 这里 pet 的 swim 和 fly 都可以访问了
let pet = getSmallPet()
if (isFish(pet)) {
pet.swim()
}
else {
pet.fly()
}
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()]
const underWater1: Fish[] = zoo.filter(isFish)
// 或者,等同于
const underWater2: Fish[] = zoo.filter(isFish) as Fish[]
// 对于更复杂的例子,该谓词可能需要重复使用
const underWatch3: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === 'frog') {
return false
}
return isFish(pet)
})
用函数类型表达式描述函数
type GreetFunction = (a: string) => void
type DescribableFunction = { //以在一个对象类型中写一个调用签名,使函数具有属性
description: string,
(someArg: number): boolean
}
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6))
}
function fn1(n:number) {
console.log(n)
return true
}
fn1.description = 'balabala...'
doSomething(fn1)
class Ctor {
s: string
constructor(s: string) {
this.s = s
}
}
type SomeConstructor = { //在调用签名前面添加 new 关键字来写一个构造签名
new (s: string): Ctor
}
function fn(ctor: SomeConstructor) {
return new ctor("hello")
}
const f = fn(Ctor)
console.log(f.s)
//在调用签名前面添加 new 关键字来写一个构造签名
interface CallOrConstruct {
new (s: string): Date;
(n?: number): number;
}
function fn(date: CallOrConstruct) {
let d = new date('2021-11-20')
let n = date(100)
}
输入的类型与输出的类型有关,或者两个输入的类型以某种方式相关,可以考虑使用泛型函数
function firstElement(arr: any[]) {
return arr[0]
}
//使用一个约束条件限制类型参数可以接受的类型 'Type extends { length: number }'
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a
} else {
return b
}
}
// longerArray 的类型是 'number[]'
const longerArray = longest([1, 2], [1, 2, 3])
// longerString 是 'alice'|'bob' 的类型
const longerString = longest("alice", "bob")
// 错误! 数字没有'长度'属性
const notOK = longest(10, 100)
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2)
}
const arr = combine<string | number>([1, 2, 3], ["hello"]) //指定类型 ''
编写优秀通用函数的准则
参数用 ? 标记为可选参数
function f(x?: number) { ... }
通过 xxx = xx 提供一个参数默认值
function f(x = 10) { ... }
function makeDate(timestamp: number): Date; //函数签名 重载签名
function makeDate(m: number, d: number, y: number): Date; //函数签名 重载签名
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date { //函数的主体 实现签名
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
//错误的!!!
const d3 = makeDate(1, 3);
//函数内This 的声明
interface User {
admin: boolean
}
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const db:DB = {
filterUsers: (filter: (this: User) => boolean) => {
let user1 = { admin: true }
let user2 = { admin: false }
return [user1, user2]
}
}
//这里不能使用箭头函数
const admins = db.filterUsers(function (this: User) {
return this.admin;
})
形参展开(Rest Parameters)
rest 参数出现在所有其他参数之后,并使用 … 的语法
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
// 'a' 获得的值 [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);
实参展开(Rest Arguments)
spread 语法从数组中提供可变数量的参数
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);
//TypeScript并不假定数组是不可变的
// 推断的类型是 number[] -- "一个有零或多个数字的数组"
// 不专指两个数字
const args = [8, 5];
const angle = Math.atan2(...args); //报错
// 推断为2个长度的元组
const args = [8, 5] as const;
const angle = Math.atan2(...args);
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) { //参数解构
console.log(a + b + c);
}
可选属性
interface PaintOptions {
shape: Shape;
xPos?: number; //可选属性
yPos?: number; //可选属性
}
只读属性
interface SomeType {
readonly prop: string; //只读属性
}
function doSomething(obj: SomeType) {
// 可以读取 'obj.prop'.
console.log(`prop has the value '${obj.prop}'.`);
// 但不能重新设置值
obj.prop = "hello";
}
//readonly 只修饰该属性本身不能被重新写入
interface Home {
readonly resident: {
name: string;
age: number
};
}
function visitForBirthday(home: Home) {
// 我们可以从'home.resident'读取和更新属性
console.log(`Happy birthday ${home.resident.name}!`);
home.resident.age++;
}
function evict(home: Home) {
// 但是我们不能写到'home'上的'resident'属性本身
home.resident = {
name: "Victor the Evictor",
age: 42,
}
}
//TypeScript在检查两个类型的属性是否兼容时,并不考虑这些类型的属性是否是 readonly ,所以 readony 属性也可以通过别名来改变
interface Person {
name: string;
age: number;
}
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
let writablePerson: Person = {
name: "Person McPersonface",
age: 42,
};
// 正常工作
let readonlyPerson: ReadonlyPerson = writablePerson;
console.log(readonlyPerson.age); // 打印 '42'
writablePerson.age++;
console.log(readonlyPerson.age); // 打印 '43'
索引签名
interface StringArray {
[index: number]: string; //索引签名 索引签名类型为number ,值类型为 string
}
const myArray: StringArray = ['a', 'b'];
const secondItem = myArray[1];
//索引签名的属性类型必须是 string 或 number
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // 正确, length 是 number 类型
name: string; // 正确, name 是 string 类型
}
//只读索引签名
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory"; //错误的
interface BasicAddress {
city: string;
country: string;
}
interface AddressWithUnit extends BasicAddress { //扩展类型
unit: string;
}
交叉类型,主要用于组合现有的对象类型
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
type ColorfulCircle = Colorful & Circle; //交叉类型
const cc: ColorfulCircle = {
color: "red",
radius: 42
}
interface Box<Type> {
contents: Type;
}
let box: Box<string>;
//通过泛型编写其他类型的通用辅助类型
type OrNull<Type> = Type | null;
type OneOrMany<Type> = Type | Type[];
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
Tuple 类型是另一种 Array 类型
type StringNumberPair = [string, number] //StringNumberPair 描述了其索引 0 包含字符串 和索引 1 包含数字 的数组
//为特定的索引声明属性,并且用数字字面类型声明长度
interface StringNumberPair {
// 专有属性
length: 2;
0: string;
1: number;
// 其他 'Array' 成员...
slice(start?: number, end?: number): Array<string | number>;
}
//可选的元组,元素只能出现在末尾,而且还影响到长度的类型
type Either2dOr3d = [number, number, number?];
type StringNumberBooleans = [string, number, ...boolean[]]; //其前两个元素分别是字符串和数字,但后面可以有任意数量的布尔
type StringBooleansNumber = [string, ...boolean[], number]; //其第一个元素是字符串,然后是任意数量的布尔运算,最后是一个数字
type BooleansStringNumber = [...boolean[], string, number]; //其起始元素是任意数量的布尔运算,最后是一个字符串,然后是一个数字
//只读元组类型
function doSomething(pair: readonly [string, number]) { ... }
keyof 运算符接收一个对象类型,并产生其键的字符串或数字字面联合
type Point = { x: number; y: number };
type P = keyof Point;
const p1:P = 'x'
const p2:P = 'y'
type Mapish = { [k: string]: boolean };
type M = keyof Mapish;
const m:M = 'a'
const m2:M = 10
//typeof 操作符引用一个变量或属性的类型
let s = "hello";
let n: typeof s;
n = 'world';
//预定义的类型 ReturnType 接收一个函数类型 并产生其 返回类型
type Predicate = (x: unknown) => boolean;
type K = ReturnType<Predicate>;
//使用typeof去修饰函数调用是不合法的!!!
// 我们认为使用 = ReturnType
let shouldContinue: typeof msgbox("Are you sure you want to continue?");
const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
/* type Person = { name: string; age: number; } */
type Person = typeof MyArray[number];
const p:Person = { name: 'xiaoqian', age: 11 }
// type Age = number
type Age = typeof MyArray[number]["age"];
const age:Age = 11
// 或者
// type Age2 = number
type Age2 = Person["age"];
const age2:Age2 = 11
SomeType extends OtherType ? TrueType : FalseType;
interface IdLabel {
id: number /* 一些字段 */;
}
interface NameLabel {
name: string /* 另一些字段 */;
}
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "unimplemented";
}
// let a: NameLabel
let a = createLabel("typescript");
// let b: IdLabel
let b = createLabel(2.8);
// let c: NameLabel | IdLabel
let c = createLabel(Math.random() ? "hello" : 42);
条件类型约束
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
interface Email { message: string; }
interface Dog { bark(): void; }
// type EmailMessageContents = string
type EmailMessageContents = MessageOf<Email>;
const emc:EmailMessageContents = 'balabala...'
// type DogMessageContents = never
type DogMessageContents = MessageOf<Dog>;
const dmc:DogMessageContents = 'error' as never;
使用 infer 关键字编写辅助类型别名。
//从函数类型中提取出返回类型
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never;
// type Num = number
type Num = GetReturnType<() => number>;
// type Str = string
type Str = GetReturnType<(x: string) => string>;
// type Bools = boolean[]
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
// 给泛型传入 string 类型,条件类型会返回 never
type Never = GetReturnType<string>
const nev:Never = 'error' as never
分布式条件类型
type ToArray<Type> = Type extends any ? Type[] : never;
// type StrArrOrNumArr = string[] | number[]
type StrArrOrNumArr = ToArray<string | number>;
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
// 'StrArrOrNumArr'不再是一个联合类型
// type StrArrOrNumArr = (string | number)[]
type StrArrOrNumArr = ToArrayNonDist<string | number>;
映射类型是一种通用类型,它使用 PropertyKeys 的联合(经常通过 keyof 创建)迭代键来创建一个类型。
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
//OptionsFlags 将从 Type 类型中获取所有属性,并将它们的值改为布尔值
type FeatureFlags = {
darkMode: () => void;
newUserProfile: () => void;
};
/*
type FeatureOptions = {
darkMode: boolean;
newUserProfile: boolean;
}
*/
type FeatureOptions = OptionsFlags<FeatureFlags>;
在映射过程中,有两个额外的修饰符可以应用: readonly 和 ? ,它们分别影响可变性和可选性。可以通过用 - 或 + 作为前缀来删除或添加这些修饰语。默认 +
type CreateMutable<Type> = {
// 从一个类型的属性中删除 "readonly"属性
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
/*
type UnlockedAccount = {
id: string;
name: string;
}
*/
type UnlockedAccount = CreateMutable<LockedAccount>;
class C implements A, B {}
class C extends A,B{}
static
类可以有静态成员。这些成员并不与类的特定实例相关联。它们可以通过类的构造函数对象本身来访问。
静态成员也可以使用相同的 public 、 protected 和 private 可见性修饰符。
静态成员也会被继承。
name 、 length 和 call 这样的函数属性,定义为静态成员是无效的。
class Foo {
static #count = 0; // #count是私有的
get count() {
return Foo.#count;
}
static { //static区块
try {
const lastInstances = { length: 100 };
Foo.#count += lastInstances.length;
}
catch {}
}
}
class Box<Type> {
contents: Type;
constructor(value: Type) {
this.contents = value;
}
}
// const b: Box
const b = new Box("hello!");
TypeScript提供了特殊的语法,可以将构造函数参数变成具有相同名称和值的类属性。这些被称为参数属性,通过在构造函数参数前加上可见性修饰符 public 、 private 、 protected 或 readonly 来创建。由此产生的字段会得到这些修饰符。
class Params {
constructor(public readonly x: number, protected y: number, private z: number)
{
// No body necessary
}
}
const a = new Params(1, 2, 3);
// (property) Params.x: number
console.log(a.x);
const someClass = class<Type> { //这是一个类表达式,与类声明非常相似,类表达式不需要名字,我们可以通过它们最终绑定的任何标识符来引用它们
content: Type; constructor(value: Type) {
this.content = value;
}
};
// const m: someClass
const m = new someClass("Hello, world");
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
const b = new Base(); //错误
class Derived extends Base {
getName() { return "world"; }
}
const d = new Derived();
d.printName();
function greet(ctor: new() => Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Base); //错误