1. 安装
输入命令:npm install -g typescript
进行全局安装typescript
,运行tsc -V
查看typescript
的版本,以及验证是否安装成功。
1.1 编写一个typescript文件
我们简单的编写一个typescript
类型的文件greeter.ts
,文件的后缀为ts
,内容如下(纯js
代码):
function greeter (person) {
return 'Hello' + person
}
let user = 'typescript'
console.log(greeter(user))
然后在cmd
终端运行命令:tsc greeter.ts
将typescript
文件转换为浏览器可以识别的ts
代码;执行完之后会看对目录中会出现一个同名的后缀为js
的文件,这个就是抓换的文件。
上面的代码我们修改一下:
function greeter (person:string) {
return 'Hello' + person
}
let user = [1, 2]
console.log(greeter(user))
如果你的编译器里面安装了ypescript
的插件会直接提示错误,我们再次终端运行命令:tsc greeter.ts
会发现报错提示,类型不匹配,上面的代码意思是我们接收的一个参数为string
类型的,但是我们传入的参数是一个数组,因此会提示错误,但是还是可以编译成功的。如果一个函数只需要一个参数,但是我们传入两个参数,也是会报错的:
function greeter (person:string) {
return 'Hello' + person
}
let user = 'typescript'
console.log(greeter(user, 'java'))
在typescript
中使用interface
关键字定义一个接口,代码如下:
// 定义一个接口,相当于定义一个对象
interface Person {
firstName: string
lastName: string
}
// 对传入的参数进行类型检验,是一个Person对象
function greeter (person:Person) {
return 'Hello' + person.firstName + '' + person.lastName
}
// 构造参数对象
let user: Person = {
firstName: 'typescript',
lastName: 'java'
}
console.log(greeter(user))
还是使用clss
关键字,具体代码如下:代码很简单。
class User {
// 全局变量,默认public
fullName:string
firstName: string
lastName: string
constructor(firstName: string, lastName: string) {
this.firstName = firstName
this.lastName = lastName
this.fullName = firstName + ' ' + lastName
}
}
interface Person {
firstName: string
lastName: string
}
function greeter (person:Person) {
return 'Hello' + person.firstName + '' + person.lastName
}
// 接口跟类的类型一样。
let user = new User('typescript', 'java')
console.log(greeter(user))
2. typescript 的类型系统
2.1 基础类型
2.2 变量声明
在进行变量的限制的时候,我们会使用?:
来限制参数,?:
是代表可选参数和可选属性;使用了–strictNullChecks
也就是加了?:
可选参数会被自动加上|undefined
,如下面的代码:
function f(x: number, y?: number) {
return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' is not assignable to 'number | undefined'
// -------------------------
class C {
a: number;
b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // error, 'undefined' is not assignable to 'number'
c.b = 13;
c.b = undefined; // ok
c.b = null; // error, 'null' is not assignable to 'number | undefined'
2.3 接口
接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。给函数传入参数的时候我们使用接口的可选属性,接口的定义也一般用作对函数传入参数的类型进行限定。如下代码:
interface SquareConfig {
interface Square {
color: string,
area: number
}
interface SquareConfig {
color?: string
width?: number
}
function createSquare (config: SquareConfig): Square {
let newSquare = {color: 'white', area: 100}
if (config.color) {
newSquare.color = config.color
}
if (config.width) {
newSquare.area = config.width * config.width
}
return newSquare
}
let mySquare = createSquare({color: 'black'})
}
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?
符号。可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
2.3.1 只读属性
对于属性的话,定义只读属性如下:
interface Point {
readonly x: number
readonly y: number
}
// --------
let p1: Point = { x: 10, y: 20 }
p1.x = 5 // error!
定义数组是只读的,如下:
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!
对于readonly
与const
的使用:如果是变量的话,一般我们是使用const
定义,如果是属性的话,我们一般是使用readonly
去定义。
2.3.2 字符串索引签名
我们在写代码的时候经常会遇到下面的情况:
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare (config: SquareConfig): { color: string; area: number } {
let newSquare = {color: 'white', area: 100}
if (config.color) {
newSquare.color = config.color
}
if (config.width) {
newSquare.area = config.width * config.width
}
return newSquare
}
// error: 'colour' 不存在于类型 'SquareConfig' 中
let mySquare = createSquare({ colour: 'red', width: 100 })
绕开这些检查非常简单。 最简便的方法是使用类型断言:
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig)
然而,最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果 SquareConfig
带有上面定义的类型的color
和width
属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
interface SquareConfig {
color?: string
width?: number
[propName: string]: any
}
也可以将要赋值的参数赋值给另一个变量,来绕过这种检查:
let squareOptions = { colour: 'red', width: 100 }
let mySquare = createSquare(squareOptions)
2.3.3 类实现一个接口
当一个类实现了一个接口时,只对其实例部分进行类型检查。constructor
存在于类的静态部分,所以不在检查的范围内。
interface ClockConstructor {
new (hour: number, minute: number)
}
// error
class Clock implements ClockConstructor {
currentTime: Date
constructor(h: number, m: number) { }
}
这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。constructor
存在于类的静态部分,所以不在检查的范围内。
2.3.4 混合类型
接口能够描述 JavaScript 里丰富的类型。 因为 JavaScript 其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。
一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性。
interface Counter {
(start: number): string
interval: number
reset(): void
}
function getCounter(): Counter {
let counter = (function (start: number) { }) as Counter
counter.interval = 123
counter.reset = function () { }
return counter
}
let c = getCounter()
c(10)
c.reset()
c.interval = 5.0
在使用JavaScript
第三方库的时候,你可能需要像上面那样去完整地定义类型
2.3.5 接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的 private
和 protected
成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。 这个子类除了继承至基类外与基类没有任何关系。例:
class Control {
private state: any
}
interface SelectableControl extends Control {
select(): void
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// Error:“ImageC”类型缺少“state”属性。
class ImageC implements SelectableControl {
select() { }
}
在上面的例子里,SelectableControl
包含了 Control
的所有成员,包括私有成员 state
。 因为 state
是私有成员,所以只能够是 Control
的子类们才能实现 SelectableControl
接口。 因为只有 Control
的子类才能够拥有一个声明于Control
的私有成员 state
,这对私有成员的兼容性是必需的。
在 Control
类内部,是允许通过 SelectableControl
的实例来访问私有成员 state
的。 实际上,SelectableControl
接口和拥有 select
方法的 Control
类是一样的。Button
和 TextBox
类是 SelectableControl
的子类(因为它们都继承自Control
并有 select
方法),但 ImageC
类并不是这样的。