我正在参加「掘金·启航计划」
TypeScript官网:www.typescriptlang.org/
介绍
全局安装
npm install -g typescript
查看验证版本号
tsc -v
编译,然后可以得到一个同名的 js 文件
tsc helloworld.ts
#如果不在根目录下,要加反斜杠
tsc .\src\helloworld.ts
也可以用ts-node插件直接在终端查看结果,但不会生成 js 文件
npm install -g ts-node
运行
ts-node helloworld.ts
类型
const isDone: boolean = false // 布尔
const num: number = 6 // 数字
const str: string = 'zgh' // 字符串
const obj: object = {} // 对象
const u: undefined = undefined
const n: null = null
数组类型
const list1: number[] = [1, 2, 3] // 由数字组成的数组
const list2: Array<number> = [4, 5, 6] // 数组泛型
const list3: [string, number] = ['1', 2] // 元组Tuple,表示一个已知元素数量和类型的数组
const list4: (number | string)[] = [1, 'a', 'b', 2] // 不知道元素数量,类型已知
const list5: [string, number] = ['ha', 666]
const [ha, info] = list5 // 解构赋值
对象数组的类型注解
const arr: { name: string; age: number }[] = [
{ name: 'tom', age: 18 },
{ name: 'jack', age: 19 }
]
如果有同样类型的数组,可以用 类型别名
type user = { name: string; age: number }
const arr: user[] = [
{ name: 'tom', age: 18 },
{ name: 'jack', age: 19 }
]
也可以使用 类
class user {
name: string
age: number
}
const arr: user[] = [
{ name: 'tom', age: 18 },
{ name: 'jack', age: 19 }
]
Symbol类型
const sym = Symbol()
let obj = {
[sym]: 'zgh'
}
console.log(obj[sym]) // zgh
枚举类型
enum Direction {
NORTH,
SOUTH,
EAST,
WEST
}
let dir: Direction = Direction.NORTH
console.log(dir) // 0
let dirName: string = Direction[2]
console.log(dirName) // EAST
Any类型
即任意类型,ts 允许对 any 类型的值进行任何操作
let notSure: any
notSure.user.name // ok
notSure[0] // ok
notSure() // ok
new notSure() // ok
unknown类型
就是不知道啥类型,只能被赋值给any类型和unknown类型本身
let unk: unknown
unk.user.name // Error
unk() // Error
new unk() // Error
let value: unknown
let value1: unknown = value // OK
let value2: any = value // OK
let value3: boolean = value // Error
let value4: number = value // Error
let value5: string = value // Error
let value6: object = value // Error
let value7: any[] = value // Error
let value8: Function = value // Error
void类型
表示没有任何类型,比如当一个函数没有返回值时
function getInfo(): void {
console.log('This is message')
}
never类型
表示永不存在的值的类型。 例如,never类型是那些总是会抛出异常、没有返回值的函数表达式、箭头函数表达式的返回值类型
function error(message: string): never {
throw new Error(message)
}
function infiniteLoop(): never {
while (true) {}
}
联合类型
以|为标记,若希望属性为多个类型中的一个,可以使用联合类型。下面的例子表示函数参数接受一个数字类型的数组或者一个字符串
let union = function(item: number[] | string) {
if (typeof item === 'string') {
return 'string'
}
return item
}
union('sss')
类型别名
使用type定义一个类型别名
// 此处直接注解name是一个string类型
let name: string
// 此处定义一个别名age
type age = string | number
let user: age
user = 123
user = 'he'
类型别名和接口的区别
type name = string
type user1 = { name: string; age: number }
interface user2 {
name: string
age: number
}
类型别名可以直接注解字符串、数字等类型,而接口只能注解对象
接口
function add(param: { one: number; two: number }): number {
return param.one + param.two
}
const total = add({ one: 1, two: 2 })
注解表示param参数是一个对象,有参数one、two且类型是number,返回值是number。也可以使用解构赋值
function add({ one, two }: { one: number; two: number }): number {
return one + two
}
使用接口
interface Person {
first: string
last: string
}
function greeter(person: Person): string {
return 'hello,' + person.first + 'and' + person.last
}
let user = { first: 'zgh', last: 'ivan' }
greeter(user)
类型检查器会检查greeter的调用,greeter 有一个参数,并要求这个对象参数有名称为first和last、类型为string的属性,
属性的顺序不会检查
interface只支持声明对象类型,可以合并扩展
interface Person {
first: string
}
interface Person {
last: string
}
const user = {} as Person
console.log(user.first)
console.log(user.last)
可选属性
接口里的属性并不都是必需的
interface SquareConfig {
color?: string
width?: number
}
function createSquare(config: SquareConfig): { color: string; area: number } {
let newSquare = { color: 'blue', area: 100 }
if (config.color) {
newSquare.color = config.color
}
if (config.width) {
newSquare.area = config.width * config.width
}
return newSquare
}
let square = createSquare({ color: 'red' })
在接口属性名称后面加?即表示非必需
createSquare()后面的{ color: string; area: number }表示返回值类型
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值,使用readonly指定只读属性
interface User {
readonly name: string
readonly age: number
}
let my: User = { name: 'zgh', age: 24 }
my.age = 23 // Error
数组也有只读属性,let arr: ReadonlyArray<number> = [1, 2, 3]
任意属性
当一个接口中有可选属性和只读属性时,还希望允许有其他的任意属性,可以用索引签名实现
interface User {
name?: string
readonly age: number
[propName: string]: any
}
let my: User = { name: 'zgh', age: 24, height: 183 }
[propName: string]: any表示属性名称是字符串类型,值是任意类型
函数类型
接口也可以描述函数类型,给接口定义一个调用签名,包括参数列表和返回值类型
interface Fun {
(name: string, age: number): boolean
}
let myFun: Fun = function(name: string, age: number) {
return age > 20
}
myFun('zgh', 23)
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配
interface Fun {
(name: string, age: number): boolean
}
let myFun: Fun = function(n: string, a: number): boolean {
return a > 20
}
函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的
可索引类型
可索引类型有一个索引签名,包括索引的类型和返回值类型,支持数字索引和字符串索引共两种签名
interface Arr {
[index: number]: string
}
let ar: Arr = ['red', 'green', 'blue']
let re = ar[1]
console.log(re) // 'green'
上面表示用number类型 1 去索引Arr时会得到一个string类型 'green'
内联类型注解
如果你不想写接口,可以直接使用内联类型注解。但是在多处使用相同注解时,建议使用接口
let user: {
name: string
age: number
}
user = {
name: 'zgh',
age: 23
}
方法
接口里可以写方法,如示例里定义一个say方法,返回值类型是字符串
interface Info {
ff: number
gg: string
say(): string
}
function getInfo2(res: Info): string {
const sayInfo = res.say()
return res.ff + res.gg
}
const user = {
gg: 'zgh',
ff: 666,
say(): string {
return 'hello world'
}
}
getInfo2(user)
断言
类型断言
类型断言就是告诉编译器你比它更懂这个类型,让它别报错,知道自己在干啥。
有尖括号<>语法和as语法两种,类似类型转换,不进行特殊的数据检查和解构。没有运行时的影响,只在编译阶段起作用。
let str1: any = 'typescript'
let res1 = <string>str1
console.log(res1) // 'typescript'
let res2: number = (<string>str1).length
console.log(res2) // 10
let str2: any = 'typescript'
let res3: number = (str2 as string).length
console.log(res3) // 10
在 TypeScript 里使用 JSX 时,只有 as 语法断言是被允许的,尖括号会和 JSX 语法产生歧义
在 typescript 中,如下代码是会报错的,因为obj类型检测就是一个空对象,不能添加其他属性
const obj = {}
obj.name = 'zgh'
obj.age = 23
使用类型断言后可以了
interface User {
name: string
age: number
}
const user = {} as User
user.name = 'zgh'
user.age = 23
索引签名
索引签名可以定义对象内的属性、值的类型
interface Foo {
[propName: string]: number
}
枚举
枚举是组织收集有关联变量的一种方式
数字枚举
enum Direction {
NORTH,
SOUTH,
EAST,
WEST
}
let dir: Direction = Direction.NORTH
console.log(dir) // 0
let dirName: string = Direction[2]
console.log(dirName) // EAST
let taoWa = Direction[Direction[Direction.SOUTH]]
console.log(taoWa) // 1
被编译成 js 后的代码
var Direction
;(function(Direction) {
Direction[(Direction['NORTH'] = 0)] = 'NORTH'
Direction[(Direction['SOUTH'] = 1)] = 'SOUTH'
Direction[(Direction['EAST'] = 2)] = 'EAST'
Direction[(Direction['WEST'] = 3)] = 'WEST'
})(Direction || (Direction = {}))
var dir = Direction.NORTH
console.log(dir) // 0
var dirName = Direction[2]
console.log(dirName) // EAST
var taoWa = Direction[Direction[Direction.SOUTH]]
console.log(taoWa) // 1
分析第三行代码,Direction["NORTH"] = 0是将Direction对象中的NORTH属性值设为 0,接着执行Direction[0] = "NORTH"
js 赋值运算符返回的是被赋予的值
let a = []
function f() {
return (a['b'] = 0)
}
f() // 0
数字枚举默认第一个值是从 0 开始,后续依次递增 1,但是也可以改变任意枚举成员关联的数字
enum Direction {
NORTH, // 0
SOUTH = 3, // 3
EAST, // 4
WEST // 5
}
字符串枚举
枚举类型的值可以是字符串
enum Direction {
NORTH = 'north',
SOUTH = 'south',
EAST = 'east',
WEST = 'west'
}
let resData = 'east' // 假设resData就是后端返回的值
let res = resData as Direction
if (res === Direction.NORTH) {
console.log('密码正确')
} else {
console.log('甩锅给后端')
}
常量枚举
enum Dir {
False,
True,
Undefined,
Null
}
const r = Dir.False
编译成 js 后
var Dir
;(function(Dir) {
Dir[(Dir['False'] = 0)] = 'False'
Dir[(Dir['True'] = 1)] = 'True'
Dir[(Dir['Undefined'] = 2)] = 'Undefined'
Dir[(Dir['Null'] = 3)] = 'Null'
})(Dir || (Dir = {}))
var r = Dir.False
可以看到const r = Dir.False编译后没啥区别,还是存在变量Dir。如果使用常量枚举可以看到简化了很多
const enum Dir {
False,
True,
Undefined,
Null
}
const r = Dir.False
// 编译后的js
var r = 0 /* False */
枚举的静态方法
使用namespace可以给枚举类型添加静态方法
例子表示是否上班(不双休的就算了),注意export不可少
enum Weekday {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
namespace Weekday {
export function isBusinessDay(day: Weekday) {
switch (day) {
case Weekday.Saturday:
case Weekday.Sunday:
return false
default:
return true
}
}
}
const mon = Weekday.Monday
const sun = Weekday.Sunday
console.log(Weekday.isBusinessDay(mon)) // true
console.log(Weekday.isBusinessDay(sun)) // false
开放式枚举
可以拆分枚举块,在延续块中必须设置初始值,否则报错
enum Color {
Red,
Green,
Blue
}
enum Color {
DarkRed = 3,
DarkGreen,
DarkBlue
}
编译后的结果如下:
;(function(Color) {
Color[(Color['Red'] = 0)] = 'Red'
Color[(Color['Green'] = 1)] = 'Green'
Color[(Color['Blue'] = 2)] = 'Blue'
})(Color || (Color = {}))
;(function(Color) {
Color[(Color['DarkRed'] = 3)] = 'DarkRed'
Color[(Color['DarkGreen'] = 4)] = 'DarkGreen'
Color[(Color['DarkBlue'] = 5)] = 'DarkBlue'
})(Color || (Color = {}))
函数
函数声明
在没有提供函数实现的情况下,有两种声明函数的方式
type f = {
(info: string): string
}
type g = (info: string) => string
表示有一个函数参数是info,参数和返回值都是字符串类型
参数注解
// 内联类型注解
function getInfo(info: string) {}
// 接口类型注解
interface Info {
ff: number
gg: string
}
function getInfo2(res: Info) {
return res.ff + res.gg
}
getInfo2({ gg: 'zgh', ff: 666 })
// 可选参数
function getInfo3(info: string, msg?: string) {}
// 参数设置默认值
function getInfo4(info: string = 'success') {}
返回值注解
通常不需要给函数返回值添加注解,编译器会推断出来
// 没有返回值
function getInfo(): void {
console.log('This is message')
}
// 返回{name: string, age: number}
function getUser(info: string): { name: string; age: number } {
return { name: info, age: 23 }
}
getUser('zgh')
函数重载
函数根据传入不同的参数而返回不同类型的数据
function f(x: string): string
function f(x: number): number
function f(x) {
if (typeof x === 'string') {
return x
} else if (typeof x === 'number') {
return x + 1
}
}
console.log(f(1))
这样改变后,重载的 f 函数在调用的时候会进行正确的类型检查。
编译器会先从重载列表的第一个重载定义开始查找匹配项,直到选择出正确的检查类型。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
注意,function f(x) {}并不是重载列表的一部分,这里只有两个,以其它参数调用 f 会报错。
类
基本使用
typescript 中的类和 ES6 中的类大部分相同
class Foo {
content: string = 'hello world'
say() {
return this.content
}
}
class Bar extends Foo {
song() {
return 'dadada'
}
}
const msg = new Bar()
console.log(msg.say()) // hello world
console.log(msg.song()) // dadada
首先声明了一个 Foo 类,有 content 属性和 say 方法。然后声明了一个 Bar 类继承 Foo 类,创建实例后调用 say 和 song 方法均可行。
类的重写,就是子类可以重新编写父类里边的代码。比如在子类 Bar 中也写一个 say 方法,可以返回其他内容。
super关键字可以调用父类中的方法
class Bar extends Foo {
song() {
return 'dadada'
}
say() {
return super.say() + ' 666'
}
}
const msg = new Bar()
console.log(msg.say()) // hello world 666
类的访问类型
类的访问类型有public、private、protected三种,分别表示公共的、私有的、受保护的。
public类型在类的内部和外部都可访问,默认的类型就是public
private类型只能在类的内部访问,外部和继承的子类都不能访问
protected类型能在类的内部和继承的子类中访问,不能在外部访问
class Foo {
private con: number = 8
protected content: string = 'hello world'
public say() {
return this.content
}
}
class Bar extends Foo {
song() {
return this.content
}
}
const msg = new Bar()
msg.say()
类的构造函数
在类 Foo 中定义一个未赋初值的 name 属性、一个赋初值的 age 属性,在创建实例时传递参数通过构造器给 name 属性赋值
class Foo {
name: string
age: number = 23
constructor(name) {
this.name = name
}
say() {
return this.age
}
}
const res = new Foo('zgh')
console.log(res) // Foo { name: 'zgh', age: 23 }
console.log(res.say()) // 23
以上写法便于理解,但是还有简便写法
class Foo {
constructor(public name: string, public age: number) {}
say() {
return this.name
}
}
const res = new Foo('zgh', 23)
console.log(res) // Foo { name: 'zgh', age: 23 }
这种写法相当于定义了 name 和 age 属性,然后在构造器中进行赋值
注意属性前面的public关键字不能少,否则报错Property 'name' does not exist on type 'Foo'.
在子类中使用构造器需要使用super()调用父类的构造器,如果有参数还需传递参数。
如果在父类中没有显示的声明构造器,也有默认的构造器constructor() {},仍需调用super()
class Foo {
constructor(public name: string, public age: number) {}
say() {
return this.name
}
}
class Bar extends Foo {
constructor(public name: string, public age: number, public msg: string) {
super(name, age)
}
}
const res = new Bar('zgh', 23, 'handsome')
console.log(res) // Bar { name: 'zgh', age: 23, msg: 'handsome' }
类的 getter、setter
假设有一个私有属性_age,外部是不能访问的,可以通过getter、setter属性从外部获取和设置
class girlFriend {
constructor(private _age: number) {}
get age() {
return this._age
}
set age(age: number) {
this._age = age
}
}
const girl = new girlFriend(18)
console.log(girl.age) // 18
girl.age = 20
console.log(girl.age) // 20
抽象类
抽象类和抽象方法都以abstract关键字开始
abstract class Hobby {
abstract skill(): string // 抽象方法没有具体逻辑,不能加大括号
}
class Song extends Hobby {
skill() {
return '唱'
}
}
class Jump extends Hobby {
skill() {
return '跳'
}
}
class Rap extends Hobby {
skill() {
return 'rap'
}
}
tsconfig.json
tsconfig.json文件是用来配置如何编译 ts 文件的,一般在项目的根目录下,使用下方命令可生成
tsc --init
默认是编译所有 ts 文件
include表示包含哪些要编译的文件,exclude表示不包含哪些文件,二者都可使用通配符
files指定一个包含相对或绝对文件路径的列表
{
"include": ["bar.ts"],
// "include": ["src/**/*"],
// "exclude": ["bar.ts"],
// "files": ["foo.ts", "bar.ts"],
"compilerOptions": {}
}
文件名称只能用双引号,用单引号会报错
泛型
泛型,generic,即泛指的类型,以尖括号 <>定义,括号里面是泛型名称,一般使用<T>表示泛型
泛型在函数中的使用
先看一个联合类型的例子,函数参数可以是字符串或者数字
function foo1(first: string | number, last: string | number) {
return `${first}-${last}`
}
foo1('hello', 'world')
使用泛型后的例子如下,定义了一个<Fan>泛型,在调用函数时要声明泛型的具体类型
function foo2<Fan>(first: Fan, last: Fan) {
return `${first}-${last}`
}
foo2<string>('hello', 'world')
foo2<number>(1, 2)
定义一种type或者interface,可以传入泛型参数,达到类型复用的效果
type Fan<T> = {
[key: string]: T
}
const obj: Fan<number> = {
a: 1,
b: 2
}
泛型在数组中的使用
有两种表示方式:Array<T> 和 T[]
function foo3<T>(first: T[]) {
return first.length
}
foo3<string>(['hello', 'world'])
function foo4<T>(first: Array<T>) {
return first.length
}
foo4<string>(['hello', 'world'])
定义多个泛型
比如定义两个泛型 T、P
function foo5<T, P>(first: T, second: P) {
return `${first}-${second}`
}
foo5<string, number>('hi', 666)
泛型在类中的使用
class Foo<T> {
constructor(private first: T[]) {}
say(index: number): T {
return this.first[index]
}
}
const gen = new Foo<string>(['hello', 'world'])
gen.say(1) // 'world'
泛型继承
上个例子中,假如传入对象数组,希望调用 say 方法返回传入的 name 值,直接改成this.first[index].name会报错
Property 'name' does not exist on type 'T'.,这时就要用到泛型的继承了
interface Person {
name: string
}
class Foo<T extends Person> {
constructor(private first: T[]) {}
say(index: number): string {
return this.first[index].name
}
}
const fff = new Foo([{ name: 'z' }, { name: 'g' }, { name: 'h' }])
fff.say(1) // 'g'
这里省略了参数类型,因为泛型的类型推断,所以也不会报错,完整的写法如下
const fff = new Foo<{ name: string }>([{ name: 'z' }, { name: 'g' }, { name: 'h' }])
泛型约束
class Foo<T> {
constructor(private first: T[]) {}
say(index: number): T {
return this.first[index]
}
}
const gen1 = new Foo<boolean>([true, false])
const gen2 = new Foo<string>(['hello', 'world'])
const gen3 = new Foo<number>([1, 2])
上面的例子可以看到泛型可以是string、number、boolean等类型,
现在进行泛型约束,使其类型只能是number或者string
class Foo<T extends number | string> {
constructor(private first: T[]) {}
say(index: number): T {
return this.first[index]
}
}
const gen2 = new Foo<string>(['hello', 'world'])
const gen3 = new Foo<number>([1, 2])