TypeScipt
中为了使编写的代码更加规范,更有利于维护,增加了类型校验,所以以后写ts
代码必须要指定类型
在
TypeScript
后面可以不指定类型,但是后期不能改变其类型,不然会报错,但是只会报错,不会阻止代码编译,因为JS
是可以允许的
1. 基本类型
1.1 布尔类型(boolean
)
let flag: boolean = true;
flag = 123; // 报错,因为不符合类型
flag = false; // 正确写法,boolean类型只能写 true 和 false
1.2 数字类型(number
)
let num: number = 123;
console.log(num);
1.3 字符串类型(string
)
let str: string = "string";
console.log(str);
1.4 数组类型(Array
)
在TypeScript
中有两种定义数组的方法
// 第一种定义数组方法
let arr1: number[] = [1.2.3]; // 一个全是数字的数组
// 第二种定义数组方法
let arr2: Array<number> = [1,2,3];
1.5 只读数组类型(ReadonlyArray
)
只读数组中的数组成员和数组本身的长度等属性都不能够修改,并且也不能赋值给原赋值的数组
let a: number[] = [1,2,3,4];
let ro: ReadonlyArray<number> = a; // 其实只读数组只是一个内置定义的泛型接口
ro[1] = 5; // 报错
ro.length = 5; // 报错
a = ro; // 报错,因为ro的类型为Readonly,已经改变了类型
a = ro as number[]; // 正确,不能通过上面的方法复制,但是可以通过类型断言的方式
1.6 元组类型(tuple
)
元组类型属于数组的一种,上面一种数组里面只能有一种类型,否则会报错,而元组类型内部可以有多种类型
// 元组类型可以为数组中的每个成员指定一个对应的类型
let arr: [number,string] = [123,"string"]; // 注意如果类型不对应也会报错
arr[2] = 456; // 报错
// 因为元组类型在声明时是一一对应的,这里只能有2个成员
1.7 枚举类型(enum
)
/*
通过:
enum 枚举名{
标识符 = 整形常数,
标识符 = 整形常数,
......
标识符 = 整形常数
};
定义一个枚举类型
*/
enum Color {
Red = 1,
Blue = 3,
Oragne = 5
}
let color: Color = Color.Red; // color为Color枚举类型,值为Color中的Red,也就是1
console.log(color); // 1
注意:
- 如果没有给标识符赋值,那么标识符的值默认为索引值
- 如果在期间给某个标识符进行了赋值,而之后的标识符没有赋值,那么之后表示符的索引依次为前面的值加
1
- 可以把标识符用引号括起来,效果不受影响
- 还可以通过反过来选择索引值来获取字符串标识符
enum Color {
Red,
Blue = 3,
"Oragne"
}
let color1: Color = Color.Red;
let color2: Color = Color.Blue;
let color3: Color = Color.Oragne;
let color4: string = Color[0]; // 通过获取索引得到标识符
console.log(color1); // 0
console.log(color2); // 3
console.log(color3); // 4
console.log(color4); // red
1.8 任意类型(any
)
任意类型的数据就像JS
一样,可以随意改变其类型
let num:any = 123;
num = "string";
console.log(num); // string
具体用法
// 如果在ts中想要获取 DOM 节点,就需要使用任意类型
let oDiv: any = document.getElementsByTagName("div")[0];
oDiv.style.backgroundColor = "red";
// 按道理说DOM节点应该是个对象,但是TypeScript中没有对象的基本类型,所以必须使用 any 类型才不会报错
1.9 undefined
和null
类型
虽然为变量指定了类型,但是如果不赋值的话默认该变量还是undefined
类型,如果没有指定undefined
直接使用该变量的话会报错,只有自己指定的是undefined
类型才不会报错
let flag1: undefined; // 也可以 let flag1: undefined = undefined;
console.log(flag); // undefined
let flag2: null = null; // 如果不指定值为null那么打印的时候会报错
console.log(flag2); // null
为变量指定多种可能的类型
let flag: number | undefined; // 这种写法就不会在没有赋值的时候报错了,因为设置了可能为undefined
console.log(flag); // undefined
flag = 123;
console.log(flag); // 123 也可以改为数值类型
// 也可以设定多个类型
let flag1: number | string | null | undefined;
flag1 = 123;
console.log(flag1); // 123
flag1 = null;
console.log(flag1); // null
flag1 = "string";
console.log(flag1); // string
1.10 void
类型
TypeScript
中的void
表示没有任何类型,一般用于定义方法的时候没有返回值,虽然也能给变量指定类型,但是void
类型只能被赋值undefined
和null
,没有什么实际意义
在
TypeScript
中函数的类型表示其返回值的类型,函数类型为void
表示其没有返回值
function run(): void {
console.log(123);
// return 不能有返回值,否则报错
}
function run1(): number {
console.log(456);
return 123; // 必须有返回值,并且返回值为number类型,否则报错
}
function run2(): any {
console.log(789); // 因为any是任意类型,所以也可以不要返回值
}
1.11 never
类型
never
类型是其他类型(包括null
和undefine
)的子类型,代表着从来不会出现的值,意味着声明never
类型的变量只能被never
类型所赋值
let a: undefined;
a = undefined;
let b: null;
b = null;
let c: never; // c不能被任何值赋值,包括 null 和 undefiend
c = (() => {
throw new Error("error!!");
})(); // 可以这样赋值
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
1.12 object
类型
object
表示非原始类型,也就是除number
,string
,boolean
,symbol
,null
和undefined
之外的类型
let stu: object = {name:"张三", age:18};
console.log(stu); // {name: "张三", age: 18}
// 也可以
let stu: {} = { name: "张三", age: 18 };
console.log(stu); // {name: "张三", age: 18}
declare function create (o: object | null): void;
// declare是一个声明的关键字,可以写在声明一个变量时解决命名冲突的问题
create({ prop: 0 }); // OK
create(null); // OK
create(42); // 报错
create("string"); // 报错
create(false); // 报错
create(undefined); // 报错
2. 高级类型
2.1 交叉类型
交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
function extend<T, U>(first: T, second: U): T & U {
let result = <any>{}; // 官方这里是T&U,但是会报错,因为不知T和U是不是对象,必须any才可以
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();
2.2 联合类型
联合类型与交叉类型很有关联,但是使用上却完全不同,我们使用的用竖线隔开每一个类型参数的时候使用的就是联合类型
联合类型表示一个值可以是几种类型之一。 用竖线( |
)分隔每个类型,所以 number | string | boolean
表示一个值可以是 number
,string
,或 boolean
function padLeft(value: string, padding: string | number) {
// ...
}
let indentedString = padLeft("Hello world", true); // errors,只能是string 或 number
如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird { // 这的报错先不管
// ...
}
let pet = getSmallPet(); // 因为没有明确返回哪个类型,所以只能确定时Fish和Bird类型中的一个
pet.layEggs(); // 我们可以调用共有的方法
pet.swim(); // errors,不能调用一个类型的方法,因为万一是Bird类型就不行了
2.3 类型保护
联合类型适合于那些值可以为不同类型的情况。 但当我们想确切地了解是否为 Fish
时,JavaScript
里常用来区分2
个可能值的方法是检查成员是否存在,但是在TypeScript
中必须要先使用类型断言
let pet = getSmallPet();
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
} else {
(<Bird>pet).fly();
}
2.3.1 用户自定义类型保护
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined; // 返回一个布尔值,然后TypeScript会缩减该变量类型
}
/*
pet is Fish就是类型谓词。谓词为 parameterName is Type 这种形式,parameterName必须是来自于当前函数签名里的一个参数名
*/
// 'swim' 和 'fly' 调用都没有问题了,因为缩减了pet的类型
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
TypeScript
不仅知道在if
分支里pet
是Fish
类型,它还清楚在else
分支里,一定 不是Fish
类型,一定是Bird
类型
2.3.2 typeof
类型保护
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
这些
typeof
类型保护只有两种形式能被识别:typeof v === "typename"
和typeof v !== "typename"
,"typename"
必须是"number"
,"string"
,"boolean"
或"symbol"
。 但是TypeScript
并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护
2.3.3 instanceof
类型保护
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5
? new SpaceRepeatingPadder(4)
: new StringPadder(" ");
}
// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 类型细化为'StringPadder'
}
instanceof
的右侧要求是一个构造函数,TypeScript
将细化为:
- 此构造函数的
prototype
属性的类型,如果它的类型不为any
的话- 构造签名所返回的类型的联合
2.3.4 可以为null
的类型
如果没有在vscode
中,直接编译的话是可以给一个其他类型的值赋值为undefined
或者null
的,但是如果编译时使用了--strictNullChecks
标记的话,就会和vscode
一样不能赋值了,并且可选参数和可以选属性会自动加上| undefined
类型
2.3.5 类型保护与类型断言
由于可以为null
的类型是通过联合类型实现,那么需要使用类型保护来去除 null
function f(sn: string | null): string {
if (sn === null) {
return "default";
} else {
return sn;
}
}
// 也可以使用下面的形式
function f(sn: string | null): string {
return sn || "default";
}
如果编译器不能够去除 null
或 undefined
,你可以使用类型断言手动去除。 语法是添加 !
后缀:identifier!
从 identifier
的类型里去除了 null
和 undefined
function broken(name: string | null): string {
function postfix(epithet: string) {
return name.charAt(0) + '. the ' + epithet; // error, 'name' is possibly null
}
name = name || "Bob";
return postfix("great");
}
// 上面的函数会报错
function fixed(name: string | null): string {
function postfix(epithet: string) {
// 这里的name强制断言不是null或者undefined,因为在嵌套函数中不知道name是否为null
return name!.charAt(0) + '. the ' + epithet; // ok
}
name = name || "Bob"; // 这里已经明确表明返回的name肯定是字符串
return postfix("great"); // 但是由于是嵌套函数,这里还不知道
}
/*
嵌套函数中: 因为编译器无法去除嵌套函数的null(除非是立即调用的函数表达式)。因为它无法跟踪所有对嵌套函数的调用,尤其是将内层函数做为外层函数的返回值。如果无法知道函数在哪里被调用,就无法知道调用时name的类型
*/
2.4 类型别名
类型别名会给一个类型起个新名字。类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何需要手写的类型
别名不会创建一个新的类型,而是创建了一个新的名字来引用那个类型
type Name = string; // 通过type关键词创建一个别名
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
同接口一样,类型别名也可以是泛型,可以添加类型参数并且在别名声明的右侧传入
type Container<T> = { value: T };
// 我们也可以使用类型别名来在属性里引用自己
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
// 与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
var people: LinkedList<Person>;
var s = people.name; // 注意这种写法在vscode中会报错,因为更加严格,必须先赋值再使用
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
类型别名不能出现在声明右侧的任何地方
type Yikes = Array<Yikes>; // 报错
2.5 字符串自变量类型
字符串字面量类型允许指定字符串必须的固定值。在实际应用中,字符串字面量类型可以与联合类型、类型保护和类型别名很好的配合。通过结合使用这些特性,可以实现类似枚举类型的字符串
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
} else if (easing === "ease-out") {
} else if (easing === "ease-in-out") {
} else {
// error! should not pass null or undefined.
}
}
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
// 只能从三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误
字符串字面量类型还可以用于区分函数重载
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}