目录
TypeScript概念
是 JavaScript 的一个超集,它可以编译成纯JavaScript,也可以在任何浏览器操作系统运行。简单来说就是JS的一个升级版本,给JS加了限制规范(接口等),也扩展了JS(类等)的能力
对比JavaScript语言(弱类型:任何对象所从属的类型再执行的时候才会确定类型、动态语言:一个变量可以赋不同数据类型的值),TS为强类型(任何对象所从属的类型都必须能在编译时刻确定)、静态语言:一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这种数据类型,不允许隐式的类型转换)
安装使用
安装编译器,安装后通过tsx 文件名,可以将ts文件转译为js文件
npm install -g typescript
安转运行环境,安装后可以直接通过ts-node 文件名直接运行ts文件。
npm install -g ts-node
项目使用
项目引入
yarn add typescript
引入后可以通过下面命令将ts文件转译为js文件
tsc 文件名(ts文件)
创建tsconfig配置文件
通过下面命令会自动创建一个tsconfig.json的ts配置文件
tsc --init
配置ts报错为中文
tsc --locale zh-CN
注意引入模块后,有些模块的申明文件需要单独引入
yarn add @types/模块名 -D
基础类型
注意和JS不同,JS区分基础类型和复杂数据类型,TS不区分,即基础类型就是数据类型,它包括JS中的6中数据类型(undefined、null、boolean、number、string、object),不包含symbol。除此之外还包含函数、数组、元祖、枚举、任意值(any)、空值(void)、Never。我们通过在变量后面加冒号跟数据类型来定义变量的类型。编译器在编译阶段时会根据定义的类型进行检查,类型不符合将报错。
布尔值、数字、字符串、对象、函数
let isDone: boolean = false; //布尔值
let money: number = 100; //数字
let str: string = 'hello world' //字符串
let obj: object = {name:'yf'} //对象
let fun: Function = (x: number, y: number) =>{console.log(x+y)}
数组
正常形式
let list: number[] = [1, 2, 3];
数组泛型
泛型是在编译期间不确定方法使用的类型(广泛之意思),在方法调用时,指定泛型具体指向类型。
let list: Array<number> = [1, 2, 3];
元祖
元组类型允许表示一个已知元素数量和类型的数组(即允许数组中有不同的变量类型)
let x: [string, number] = ['hello', 10];
注意当访问一个越界的元素,会使用联合类型替代,即我们可以给x[2]赋值string或者number类型的值。
x[2] = 'world'; //OK
x[3] = 2; //OK
x[4] = true //这里会报错,string和number的联合类型不能是boolean。
枚举(enum)
理解为定义一个变量,变量有所有的取值,通过不同的属性找到取值。枚举值默认从0开始,依次加1。也可以通过自己设定。
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
alert(colorName); // 显示'Green'因为上面代码里它的值是2.
注意不同枚举变量是不可以比较的。
enum Color1 { red, blue }
enum Color2 { red, blue }
Color1.red === 0 // true
Color1.red === Color2.red //error 无法进行比较
从枚举值到枚举名可以反向映射。
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
编译成
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"
常数枚举
一个枚举类型可以包含零个或多个枚举成员。 枚举成员具有一个数字值,它可以是常数或是计算得出的值 当满足如下条件时,枚举成员被当作是常数:
- 不具有初始化函数并且之前的枚举成员是常数。 在这种情况下,当前枚举成员的值为上一个枚举成员的值加1。 但第一个枚举元素是个例外。 如果它没有初始化方法,那么它的初始值为
0
。 - 枚举成员使用常数枚举表达式初始化。 常数枚举表达式是TypeScript表达式的子集,它可以在编译阶段求值。 当一个表达式满足下面条件之一时,它就是一个常数枚举表达式:
- 数字字面量
- 引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的) 如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用。
- 带括号的常数枚举表达式
+
,-
,~
一元运算符应用于常数枚举表达式+
,-
,*
,/
,%
,<<
,>>
,>>>
,&
,|
,^
二元运算符,常数枚举表达式做为其一个操作对象。 若常数枚举表达式求值后为NaN
或Infinity
,则会在编译阶段报错。
const enum Directions {
Up=3,
Down,
Left=6,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
console.log(directions)//[3,4,6,7]
任意值(any)
不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用any
类型来标记这些变量,当数组中包含了不同的类型的数据时也可以使用。
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
let list: any[] = [1, true, "free"];
注意不同的类型无法直接赋值,可以通过先断言成unknown或any再断言成相同类型进行赋值。
空值(void)
它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是void。
function warnUser(): void {
alert("This is my warning message");
}
const foo = (): void => { alert("This is my warning message"); }
注意声明一个void
类型的变量只能为它赋予undefined
和null
let unusable: void = undefined;
null、undefined
null
和undefined
是所有类型的子类型。 就是说你可以把null
和undefined
赋值给number
类型的变量。
let str: string = null //OK
str = 'hello' //OK
注意当你指定了--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自。
Never
表示永不存在的值的类型,理解为函数的返回或抛出错误或函数无法正常执行到结束。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
never
类型是任何类型的子类型,也可以赋值给任何类型;但是没有类型是never
的子类型或可以赋值给never
类型(除了never
本身之外)。 即使any
也不可以赋值给never
。
unknown
unknown
是上面所有类型的顶层类型,任何值可以赋unknown类型,unknown类型的值不能直接赋值给其他变量,且不能调用属性和方法,如果需要调用属性和方法,需要类型断言或类型保护(typeof)。
let value:unknown;
value = 'hello';
(value as string).length //类型断言
let value:unknown;
if (typeof value === 'string') { //类型保护
value.length
}
注意联合类型中有unknown,那么最终得到的都是unknown类型 。
不同的类型无法直接赋值,可以通过先断言成unknown或any再断言成相同类型进行赋值。
类型断言
告诉计编译器,我确定是什么类型的数据,编译器不会进行类型检查。
注意不能断言无重叠关系的数据类型,但是可以通过unknown和any进行联系。
let a: number=1
let b: string = a as string
//类型number到类型string的转换可能是错误的,因为两种类型不能充分重叠。
let b: string = a as unkown as string //OK
let b: string = a as any as string //OK
用法
当不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法,此时可以用类型断言访问非共有方法。
注意类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。
function func(val: string | number): number {
if (val.length) {
return val.length
} else {
return val.toString().length
}
}
//上面定义方法会报错,采用下面2种方法
尖括号语法
<类型>变量
function func(val: string | number): number {
if ((<string>val).length) {
return (<string>val).length
} else {
return val.toString().length
}
}
注意tsx中无法使用,只能使用as语法。
as语法
变量 as 类型
function func(val: string | number): number {
if ((val as string).length) {
return (val as string).length
} else {
return val.toString().length
}
}
绕过属性检查
例如下面通过类型断言成符合条件的类型,绕过属性检查。
function sayName(person: {name: string; age: number}){
return person.name
}
sayName({name: 'yf', age: 18}) //OK
sayName({}) //error
sayName({} as {name: string; age: number}) //OK
非空断言操作符 !.
用法: obj!.prop
功能: 这个操作符告诉TypeScript编译器你确定该表达式绝对不会为null或undefined,从而避免了类型检查时的警告。需要注意的是,这仅影响编译时的类型检查,并不会改变运行时的行为,因此如果实际上该表达式为null或undefined,依然会导致运行时错误。
let user: User | null = fetchUserData();
console.log(user!.name); // 告诉TypeScript你确定user不是null或undefined
变量申明 (let等)
同es6
ES6 let注意点、解构(重命名、默认值、解构给已有变量、多层解构)、模块化(注意点、导入导出语法)、对象属性扩展写法_AIWWY的博客-优快云博客
接口(interface)
https://blog.youkuaiyun.com/AIWWY/article/details/123263581
类
同es6中的类大体一样 ,区别在下面
类的实例部分和静态部分
实例部分指类的属性和方法,通过实例点属性名可以访问到的部分。
静态部分是指通过类名点属性名访问到的部分,通常被static所修饰。
类的实例变量只有实例部分结构相同才能赋值
类中如果有私有变量或保护变量则只有父子类关系的才能相互赋值
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee;// 错误: Animal 与 Employee 不兼容.
修饰符
public(公共)
为默认的修饰符,和es6中类相同,不加任何约束。
private
不能在声明它的类的外部访问。即不能通过实例点属性名访问,只能调用类的内部方法,通过类中已定义好的方法体里访问。
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // 错误: 'name' 是私有的.
注意在派生类中也无法访问,仅在自己类中定义的方法体中可以访问。
protected(受保护)
和private相似,不同在于可以在派生类中访问。
注意构造函数也可以被protected修饰,即不可以在类外访问,但可以通过继承的方式访问。
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee 能够继承 Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.
readonly
readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.
参数属性
可以直接在构造函数的参数中添加修饰符来表示属性的类型。例如下面的name为私有类型。
class Animal {
constructor(private name: string) { }
move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
抽象类(abstract)
使用abstract
关键字定义抽象类和在抽象类内部定义抽象方法。和接口类似,理解专门用来约束类的定义。故抽象类不能实例化。
抽象类可以包含成员的实现细节。 抽象方法必须包含abstract
关键字并且可以包含访问修饰符。并且一定要在继承的类中实现抽象方法。并且在继承的类中可以定义抽象类中定义的方法之外的方法。
注意用抽象类作为接口的时候不能调用抽象类中定义之外的方法。
例如下面department: Department把抽象类作为接口约束,故不能在实例中调用generateReports函数,
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // 允许创建一个对抽象类型的引用
department = new Department(); // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
department.generateReports(); // 错误: 由于抽象类作为接口限制,故不能在实例中调用抽象类中定义之外的方法。
函数
ES6 函数扩展(参数解构、length和name属性、参数作用域、严格模式、参数的尾逗号、new.target属性、toString、...args参数)和箭头函数(this指向、其它注意点)_AIWWY的博客-优快云博客同es6的函数相同,区别在下面
函数类型(约束)
注意返回值类型是函数类型的必要部分,如果函数没有返回任何值必须指定返回值类型为void
而不能留空。
let myAdd: (x:number, y:number) => void =
function(x: number, y: number): number { console.log(x + y) };
可选参数和默认参数
参数名旁使用?
实现可选参数的功能,参数名旁使用=值实现默认参数的功能。
可选参数必须跟在必须参数后面。带默认值的参数不需要放在必须参数的后面。
编译器会检查用户是否为每个参数都传入了值。 编译器还会假设只有这些参数会被传递进函数。 简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致,类型也需要一致。
注意传入undefined时相当于没有传入值。
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", null); //error Argument of type 'null' is not assignable to parameter of type 'string | undefined'
泛型
一种类型变量,只用于表示类型而不是值。在具体使用的时候传入具体类型即可。
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString"); // type of output will be 'string'
注意不能访问T定义变量的属性,因为T可能是任何类型,不是每个类型都具有该属性。
例如 下面直接访问arg.length是错误的,因为arg也可能是数组等。可以通过申明数组的方式灵活使用。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
字面量定义泛型函数
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;
泛型接口
写法一
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
写法二
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
接口约束泛型
可以定义接口约束泛型,例如下面约束泛型必须具有length属性
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
keyof
用于描述泛型上的属性。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
相同类型之间的赋值
函数
参数
x
是否能赋值给y
,首先看它们的参数列表。 x
的每个参数必须能在y
里找到对应类型的参数。右边的参数要在左边有对应的。左>=右。
注意可选参数和默认参数可以不用管。
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Error
返回值
源函数的返回值类型必须是目标函数返回值类型的子类型。即左边的属性必须在右边存在。右>=左 。
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});
x = y; // OK
y = x; // Error because x() lacks a location property
枚举
不同枚举类型之间是不能相互赋值。
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
status = Color.Green; //error
类
只有实例部分相同才能相互赋值(和静态部分无关)且如果有私有或保护成员,则只有父子类之间的关系才能相互赋值
泛型
看具体传入类型后的结构,根据对应类型的规则。
例如下面可以相互赋值,因为接口的类型没有用到,
interface NotEmpty<T> {
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; // OK
TypeScript文档
5分钟了解 TypeScript - TypeScript 中文手册
以上为链接中高级类型之前总结,。