TypeScript
数据类型
-
布尔
boolean
布尔类型与js的一致,接收
true
和false
-
数字
number
数字类型可以接受2进制、8进制、10进制、16进制
-
字符串
string
和
JavaScript
一样
可以使用''
、""
、``
-
数组
[]
数组类型,可以规定数组内的类型,也可以是任意值
// 元素类型[]或者Array<元素类型> let arr: number[] = [1, 2, 3] let arr: Array<number> = [1, 2, 3]
-
元组
Tuple
和数组类似,元组类型允许表示一个‘已知元素数量’和类型的数组
let arr: [string, number] = ['1', 2] arr = [1, '2'] // !error // 越界的值只限制类型
-
枚举
enum
枚举类型是对JavaScript标准数据类型的一个补充
enum Color {Red, Green, Blue} // 正常来讲,下标从0开始,当然也可以手动指定 enum Color {Red = 1, Green, Blue} // green = 2; blue = 3 enum Color {Red = 1, Green = 2, Blue = 4}
-
任意值
any
顾名思义:任意值,可以用在选择性的忽略类型检查或者在开发时不清楚类型的变量
你有一个数组,它包含了不同的类型的数据let arr: any[] = [1, true, '1']
-
对象
Object
声明一个对象,必须先声明内部拥有的属性,否则会报错
let obj: Object = {} obj.a = 1 会报错 // 声明一个变量,就算他内部确实有该方法,调用也会报错 let num: Object = 1 num.toFixed() // Error: Property 'toFixed' doesn't exist on type // 必须声明为 number/any
-
空值
void
空值和
any
正好相反,代表没有任何类型,只能是null
或者undefined
当一个函数没有返回值时,他的返回值类型一般为void
function getnum (): void {}
如果用void来声明变量,你只能给他赋值
null
或者undefined
-
null
、undefined
默认情况下
null
和undefined
是所有类型的子类型。 就是说你可以把null和undefined赋值给number
类型的变量。
然而,当你指定了--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自。 -
never
never
类型表示的是那些永不存在的值的类型。 例如,never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是never
类型,当它们被永不为真的类型保护所约束时。
never
类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never
的子类型或可以赋值给never
类型(除了never
本身之外)。 即使any
也不可以赋值给never
。
类型断言
有时候你会遇到这样的情况,你会比TypeScript
更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
类型断言有两种形式。
- “尖括号”语法:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
- as语法:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有as语法断言是被允许的。
接口
ts的核心之一
// 用常见的request封装来看
function request(options: { url: string; method: string; headers: object; data: object; }): never {}
// 他接收了四个参数且都限定了类型
可以将代码的可读性提高一些
interface requestOptions {
url: string;
method: string;
headers: object;
data: object;
}
function request (options: requestOptions): never {}
有些时候,我们不需要headers
或者data
参数,那么我们可以将他改为非必需参数,只需要在后面加上一个?
即可
interface requestOptions {
url: string;
method: string;
headers?: object;
data?: object;
}
function request (options: requestOptions): never {}
这时候我们如果去读取一个未被接口定义的属性是,会抛出错误
interface requestOptions {
url: string;
method: string;
headers?: object;
data?: object;
}
function request (options: requestOptions): string {
return options.params
// Error: Property 'params' does not exist on type 'options'
}
request({
url: 'https://example.com',
method: 'get',
})
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用readonly来指定只读属性
interface location {
readonly lng: number;
readonly lat: number;
}
let location: location = {
lng: 120,
lat: 100
}
location.lng = 110
// error!
关于只读,ts还有另一个类型 ReadonlyArray<T>
表示一个
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
可以看到,这种类型无法通过任何手段改变值,甚至无法赋值给其他变量
除非你使用类型断言
a = ro as number[];
不过这就没有任何意义了
最简单判断该用readonly
还是const
的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const
,若做为属性则使用readonly
。
额外的属性检查
在前面的例子里使用了接口,
TypeScript
让我们传入{url: string, method: string, headers: object, data: object}
到仅期望得到{ url: string, method: string }
的函数里。 我们已经学过了可选属性,并且知道他们在“option bags”模式里很有用。然而,天真地将这两者结合的话就会像在JavaScript里那样搬起石头砸自己的脚。
interface requestOptions {
url: string;
method: string;
headers?: object;
data?: object;
}
function request (options: requestOptions): never {}
request({
url: 'https://example.com',
method: 'get',
params: {
id: 1
}
})
// error: 'params' not expected in type 'requestOptions'
// 简单的处理方式是绕过属性检查
request({
url: 'https://example.com',
method: 'get',
params: {
id: 1
}
} as requestOptions)
// 还有另一种处理方式
// 添加一个字符串索引签名
// 前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性
// 如果这个方法出了带上必须参数,还会带上某些未知参数,只需要加上[propName: string]: any 即可
interface requestOptions {
url: string;
method: string;
headers?: object;
data?: object;
[propName: string]: any;
}
要留意,在像上面一样的简单代码里,你可能不应该去绕开这些检查。 对于包含方法和内部状态的复杂对象字面量来讲,你可能需要使用这些技巧,但是大部额外属性检查错误是真正的bug。
获取有人发现了,上面的函数()
后面{}
前面,还有一个类型定义,那是用来定义返回值的,这个后续会讲到
学完了简单的,来学点复杂的
接口能够描述
JavaScript
中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型、可索引的类型、类类型、混合类型。他也可以像类一样继承
函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string) {
let result = src.search(sub);
return result > -1;
}
如果你不想指定类型,TypeScript
的类型系统会推断出参数类型,因为函数直接赋值给了SearchFunc
类型变量。
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) {
let result = src.search(sub);
return result > -1;
}
可索引的类型
可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
类类型
与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。
interface ClockInterface {
currentTime: Date;
// 你也可以在接口中描述一个方法,在类里实现它
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
// 并不检查这部分
getTime() {
return this.currentTime
}
constructor(h: number, m: number) { }
}
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。
因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor
存在于类的静态部分,所以不在检查的范围内。
因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口,ClockConstructor
为构造函数所用和ClockInterface
为实例方法所用。 为了方便我们定义一个构造函数createClock
,它用传入的类型创建实例。
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
// 因为createClock的第一个参数是ClockConstructor类型,在createClock(AnalogClock, 7, 32)里,会检查AnalogClock是否符合构造函数签名。
继承接口
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
// 类型断言 跳过检查
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
混合类型
先前我们提过,接口能够描述JavaScript里丰富的类型。 因为JavaScript其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。
一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性。
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
// 使用类型断言越过检查
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;