1. 安装 TypeScript
命令行运行如下命令,全局安装 TypeScript:
npm install -g typescript
安装完成后,在控制台运行如下命令,检查安装是否成功:
tsc -V
2. 手动编译代码
编写 TS 程序
src/helloworld.ts
function greeter (person:string) {
return 'Hello, ' + person
}
let user = 'Yee'
console.log(greeter(user))
对helloworld.ts
文件,运行以下命令:
tsc helloworld.ts
持续监听文件变动 ts 自动编译成 js :
tsc --watch helloworld.ts
输出结果为一个 helloworld.js
文件。
在命令行上,通过 Node.js 运行这段代码:
node helloworld.js
控制台输出:
Hello, Yee
3. 基础类型
-
基本类型:
string
、number
、boolean
、symbol
、bigint
、null
、undefined
-
引用类型:
array
、Tuple
(元组)、object
(包含Object
和{}
)、function
-
特殊类型:
any
、unknow
、void
、nerver
、Enum
(枚举) -
其他类型:
类型推理
、字面量类型
、交叉类型
数组
有两种定义方法:
第一种,可以在元素类型后面接上[ ],表示由此类型元素组成的一个数组:
let list1: number[] = [1, 2, 3]
第二种方式是使用数组泛型,Array<元素类型>:
let list2: Array<number> = [1, 2, 3]
元组
允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
。 比如,你可以定义一对值分别为 string
和 number
类型的元组。
let t1: [string, number]
t1 = ['hello', 10] // OK
t1 = [10, 'hello'] // Error
当访问一个已知索引的元素,会得到正确的类型:
console.log(t1[0].substring(1)) // OK
console.log(t1[1].substring(1)) // Error, 'number' 不存在 'substring' 方法
枚举(enum)
使用枚举类型可以为一组数值赋予友好的名字
。
enum Color {
Red,
Green,
Blue
}
// 枚举数值默认从0开始依次递增
// 根据特定的名称得到对应的枚举数值
let myColor: Color = Color.Green // 1
console.log(myColor, Color.Red, Color.Blue) // 1 0 2
默认情况下,从 0
开始为元素编号。 也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1
开始编号:
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green // 2
或者,全部都采用手动赋值:
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Blue // 4
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为 2,但是不确定它映射到 Color 里的哪个名字,我们可以查找相应的名字:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]
console.log(colorName) // 'Green'
4. 联合类型、类型断言、类型推断
联合类型
表示取值可以为多种类型中的一种
需求1: 定义一个函数得到一个数字或字符串值的字符串形式值
function toString2(x: number | string) : string {
return x.toString()
}
类型断言
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号”语法, 另一个为 as
语法
/*
类型断言(Type Assertion): 可以用来手动指定一个值的类型
语法:
方式一: <类型>值
方式二: 值 as 类型 tsx中只能用这种方式
*/
/* 需求: 定义一个函数得到一个字符串或者数值数据的长度 */
function getLength(x: number | string) {
if ((<string>x).length) {
return (x as string).length
} else {
return x.toString().length
}
}
console.log(getLength('abcd'), getLength(1234))
类型推断
类型推断: TS会在没有明确的指定类型的时候推测出一个类型
有下面2种情况: 1. 定义变量时赋值了, 推断为对应的类型. 2. 定义变量时没有赋值, 推断为any类型
/* 定义变量时赋值了, 推断为对应的类型 */
let b9 = 123 // number
// b9 = 'abc' // error
/* 定义变量时没有赋值, 推断为any类型 */
let b10 // any类型
b10 = 123
b10 = 'abc'
5. 函数
// 命名函数
function add(x, y) {
return x + y
}
// 匿名函数
let myAdd = function(x, y) {
return x + y;
}
// 完整写法
let myAdd2: (x: number, y: number) => number =
function(x: number, y: number): number {
return x + y
}
可选参数、默认参数、剩余参数、函数重载
/*
函数重载: 函数名相同, 而形参不同的多个函数
需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也可以接收2个number类型的参数进行相加
*/
// 重载函数声明
function add (x: string, y: string): string
function add (x: number, y: number): number
// 定义函数实现
function add(x: string | number, y: string | number): string | number {
// 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add(1, 2))
console.log(add('a', 'b'))
// console.log(add(1, 'a')) // error
6. 接口
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。使用接口(Interfaces)来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)
readonly
只读属性
?
可选属性,可以对可能存在的属性进行预定义
interface IPerson {
readonly id: number
name: string
age: number
sex?: string
}
函数类型
接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
/*
接口可以描述函数类型(参数的类型与返回的类型)
*/
interface SearchFunc {
(source: string, subString: string): boolean
}
这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
const mySearch: SearchFunc = function (source: string, sub: string): boolean {
return source.search(sub) > -1
}
console.log(mySearch('abcd', 'bc'))
7. 类
/*
类的基本定义与使用
*/
// 定义一个类
class Greeter {
// 声明属性
message: string
// 构造方法
constructor (message: string) {
// 初始化值
this.message = message
}
// 一般方法
greet (): string {
return 'Hello ' + this.message
}
}
// 创建类的实例化对象
const greeter = new Greeter('world')
// 调用实例的方法
console.log(greeter.greet()) // Helloworld
继承
/*
类的继承
*/
class Animal {
run (distance: number) {
console.log(`Animal run ${distance}m`)
}
}
class Dog extends Animal {
cry () {
console.log('wang! wang!')
}
}
const dog = new Dog()
dog.cry()
dog.run(100) // 可以调用从父中继承得到的方法
这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里,Dog
是一个 派生类
,它派生自 Animal
基类,通过 extends
关键字。 派生类通常被称作子类,基类通常被称作超类。
因为 Dog
继承了 Animal
的功能,因此我们可以创建一个 Dog
的实例,它能够 cry()
和 run()
。
子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑
super 可以调用父类上的方法和属性
修饰符
public
,默认,可以自由访问
private
,不能在声明它的类的外部访问
protected
,类内部和子类可以访问
readonly
,将属性设置为只读,只读属性必须在声明时或构造函数里被初始化。
存取器
TypeScript
支持通过 getters/setters
来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
下面来看如何把一个简单的类改写成使用 get
和 set
。 首先,我们从一个没有使用存取器的例子开始。
class Person {
firstName: string = 'A'
lastName: string = 'B'
get fullName () {
return this.firstName + '-' + this.lastName
}
set fullName (value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
const p = new Person()
console.log(p.fullName) // A-B
p.firstName = 'C'
p.lastName = 'D'
console.log(p.fullName) // C-D
p.fullName = 'E-F'
console.log(p.firstName, p.lastName) // E F
静态属性
静态属性存在于类本身上面而不是类的实例上。
/*
静态属性, 是类对象的属性
非静态属性, 是类的实例对象的属性
*/
class Person {
name1: string = 'A'
static name2: string = 'B'
}
console.log(Person.name2) // 此处直接访问Person类的静态属性 B
console.log(new Person().name1) // A
抽象类
抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。 abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。
/*
抽象类
不能创建实例对象, 只有实现类才能创建实例
可以包含未实现的抽象方法
*/
abstract class Animal {
abstract cry ()
run () {
console.log('run()')
}
}
class Dog extends Animal {
cry () {
console.log(' Dog cry()')
}
}
const dog = new Dog()
dog.cry() // Dog cry()
dog.run() // run()
类实现接口
与 C# 或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。
/*
类类型: 实现接口
1. 一个类可以实现多个接口
2. 一个接口可以继承多个接口
*/
interface Alarm {
alert(): any;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
一个类可以实现多个接口
class Car2 implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
接口继承接口
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
interface LightableAlarm extends Alarm, Light {
}
接口和接口之间叫继承(extends),类和接口之间叫实现(implements)
8. 泛型
指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。
语法
泛型的语法是 <>
里写类型参数,一般可以用 T
来表示。
function print<T>(arg:T):T {
console.log(arg)
return arg
}
多个泛型参数的函数
一个函数可以定义多个泛型参数
function swap <K, V> (a: K, b: V): [K, V] {
return [a, b]
}
const result = swap<string, number>('abc', 123)
console.log(result[0].length, result[1].toFixed())
泛型接口
interface IbaseCRUD <T> {
data: T[]
add: (t: T) => void
getById: (id: number) => T
}
class User {
id?: number; //id主键自增
name: string; //姓名
age: number; //年龄
constructor (name, age) {
this.name = name
this.age = age
}
}
class UserCRUD implements IbaseCRUD <User> {
data: User[] = []
add(user: User): void {
user = {...user, id: Date.now()}
this.data.push(user)
console.log('保存user', user.id)
}
getById(id: number): User {
return this.data.find(item => item.id===id)
}
}
const userCRUD = new UserCRUD()
userCRUD.add(new User('tom', 12))
userCRUD.add(new User('tom2', 13))
console.log(userCRUD.data)
泛型类
在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
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
}
let myGenericString = new GenericNumber<string>()
myGenericString.zeroValue = 'abc'
myGenericString.add = function(x, y) {
return x + y
}
console.log(myGenericString.add(myGenericString.zeroValue, 'test'))
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12))
泛型约束
如果我们直接对一个泛型参数取 length
属性, 会报错, 因为这个泛型根本就不知道它有这个属性
// 没有泛型约束
function fn <T>(x: T): void {
// console.log(x.length) // error
}
我们可以使用泛型约束来实现
interface Lengthwise {
length: number;
}
// 指定泛型约束
function fn2 <T extends Lengthwise>(x: T): void {
console.log(x.length)
}
我们需要传入符合约束类型的值,必须包含必须 length
属性:
fn2('abc')
// fn2(123) // error number没有length属性
9. 其他
声明文件:
一般声明文件都会单独写成一个 xxx.d.ts
文件
declare var jQuery: (selector: string) => any;
jQuery('#foo');
很多的第三方库都定义了对应的声明文件库, 库文件名一般为 @types/xxx
, 可以在 https://www.npmjs.com/package/package
进行搜索
有的第三库在下载时就会自动下载对应的声明文件库(比如: webpack),有的可能需要单独下载(比如jQuery/react)