http://docs.vikingship.xyz/typescript.html 在线IDE:https://www.typescriptlang.org/zh/play
安装 Typescript
Typescript 官网地址: https://www.typescriptlang.org/zh/
安装 Typescript:
npm install -g typescript
使用 tsc 全局命令:
// 查看 tsc 版本
tsc -v
// 编译 ts 文件
tsc fileName.ts
使用ts-node:
npm install -g ts-node
ts-node fileName.ts
基本类型
Typescript 文档地址:Basic Types
原始数据类型
JavaScript 的原始数据类型:
- boolean / number / string / symbol / bigint
- undefined / null
```typescript let isDone: boolean = false
let age: number = 10 let binaryNumber: number = 0b1111
let firstName: string = ‘viking’
let message: string = Hello, ${firstName}, age is ${age}
// 还有undefined 和 null,不常用 let u: undefined = undefined let n: null = null
// undefined 和 null 是所有类型的子类型。 // TS 的非严格模式下,可赋值给其他类型。 let num: number = undefined
> 如果指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自,不然会报错。
<a name="z35Q6"></a>
## object
object 表示非原始类型,使用 object 类型,就可以更好的表示像 Object.create 这样的 AP
```javascript
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
void
- 声明一个void类型的变量没有什么大用,因为你只能为它赋值 undefined和null
- 当一个函数没有返回值时,你通常会见到其返回值类型是 void ```javascript let unusable: void = undefined;
function warnUser(): void { console.log(“This is my warning message”); }
<a name="QKqR9"></a>
## never/any/unkown
- `never`类型是那些总是会抛出异常 或 根本就不会有返回值的函数
```typescript
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
never 类型是任何类型的子类型,也可以赋值给任何类型。 没有类型是 never 的子类型,没有类型可以赋值给 never 类型(除了 never 本身之外)
any
类型,回到了JavaScript。let notSure: any = 4
notSure = 'maybe it is a string'
notSure = 'boolean'
// 在任意值上访问任何属性都是允许的:
notSure.myName
// 也允许调用任何方法:
notSure.getName()
unkown
当我们在写应用的时候可能会需要描述一个我们还不知道其类型的变量。
// 把 param 定义为 unknown 类型 ,TS 编译器就能拦住潜在风险
function divide(param: unknown) {
return param / 2; // 会提醒错误
}
数组 Array
```typescript / 子元素是数字类型的数组 */ let arr1: number[] = [1, 2, 3]; / 子元素是字符串类型的数组 */ let arr2: string[] = [‘x’, ‘y’, ‘z’];
//使用 Array 泛型
/ 子元素是数字类型的数组 */
let arr3: Array
> 推荐使用 `[] `这种形式来定义数组。一方面可以避免与 JSX 的语法冲突,另一方面可以减少不少代码量
<a name="X9GAn"></a>
## 元组 Tuple
元组类型是另一种类型的Array,它确切地知道元素数量及其特定位置的类型。
```typescript
// Declare a tuple type
type StringNumberPair = [string, number];
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
枚举 Enums
// 默认情况下,从 0 开始为元素编号。 你也可以手动的指定成员的数
enum Direction {
Up, // 相当于 = 0
Down,
Left,
Right,
}
console.log(Direction.Up) //0
// 反向映射
console.log(Direction[0])
// 如果枚举第一个元素赋有初始值,就会从初始值开始递增
enum Direction {
Up = 6,
Down,
Left,
Right
}
// 字符串枚举
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
const value = 'UP'
if (value === Direction.Up) {
console.log('go up!')
}
接口 interface
Duck Typing 概念:如果某个东西长像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。
// 我们定义了一个接口 Person
interface Person {
name: string;
age: number;
}
let viking: Person ={
name: 'viking',
age: 20
}
//可选属性:
interface Person {
name: string;
age?: number;
}
let viking: Person = {
name: 'Viking'
}
//只读属性
interface Person {
readonly id: number;
name: string;
age?: number;
}
viking.id = 9527 // error
除了描述带有属性的普通对象外,接口也可以描述函数类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
函数
Typescript 文档地址:Functions
function add(x: number, y: number): number {
return x + y
}
// 可选参数
function add(x: number, y: number, z?: number): number {
if (typeof z === 'number') {
return x + y + z
} else {
return x + y
}
}
// 函数本身的类型
let myAdd: (x:number, y:number) => number =
function(x: number, y: number): number { return x + y; };
高级类型
类型推导
TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型
let x = 3; // 变量x的类型被推断为数字。
// 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时。
联合类型
联合类型表示一个值可以是几种类型之一
let numberOrString: number | string
// 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
numberOrString.length
numberOrString.toString()
交叉类型
交叉类型是将多个类型合并为一个类型
interface IName {
name: string
}
type IPerson = IName & { age: number }
let person: IPerson = { name: 'hello', age: 12}
字面量类型
类似枚举
type Easing = "ease-in" | "ease-out" | "ease-in-out";
类型断言
// 这里我们可以用 as 关键字,告诉typescript 编译器,
function getLength(input: string | number): number {
const str = input as string
if (str.length) {
return str.length
} else {
const number = input as number
return number.toString().length
}
}
类型别名
类型别名会给一个类型起个新名字
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
与 interface 对比:
- type 可以声明基本类型别名,联合类型,元组等类型
- interface 可以多次定义并能够合并,可以被类实现
- interface 是一种关系结构的描述,里面可以包含属性和方法,可派生
- type 是一种表达式,所以也可以说是一种alias
- 用 interface 描述数据结构,用 type 描述类型关系
- 能用 interface 实现,就用 interface , 如果不能就用 type
- 都可以定义一个对象或函数
- 都可以继承:interface 使用 extends 实现继承, type 使用交叉类型实现继承
最佳实践:
- 平时开发中,一般使用组合或者交叉类型的时候,用 type。
- 一般要用类的 extends 或 implements 时,用 interface。
- 定义对象或函数,两者都可以
类型守卫
// typescript 在不同的条件分支里面,智能的缩小了范围,这样我们代码出错的几率就大大的降低了。
function getLength2(input: string | number): number {
if (typeof input === 'string') {
return input.length
} else {
return input.toString().length
}
}
类 Class
``typescript class Animal { name: string; constructor(name: string) { this.name = name } run() { return
${this.name} is running` } } const snake = new Animal(‘lily’)
// 继承的特性
class Dog extends Animal {
bark() {
return ${this.name} is barking
}
}
const xiaobao = new Dog(‘xiaobao’) console.log(xiaobao.run()) console.log(xiaobao.bark())
// 这里我们重写构造函数,注意在子类的构造函数中,必须使用 super 调用父类的方法,要不就会报错。 class Cat extends Animal { constructor(name) { super(name) console.log(this.name) } run() { return ‘Meow, ‘ + super.run() } } const maomao = new Cat(‘maomao’) console.log(maomao.run())
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
<a name="Os9z6"></a>
## 抽象类
所谓抽象类,是指只能被继承,但不能被实例化的类,就这么简单。<br />抽象类有两个特点:
- 抽象类不允许被实例化
- 抽象类中的抽象方法必须被子类实现
> 如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
```typescript
abstract class Animal {
constructor(name:string) {
this.name = name
}
public name: string
public abstract sayHi():void
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
public sayHi() {
console.log('wang')
}
}
类与接口
interface Radio {
switchRadio(trigger: boolean): void;
}
class Car implements Radio {
switchRadio(trigger) {
return 123
}
}
class Cellphone implements Radio {
switchRadio() {
}
}
interface Battery {
checkBatteryStatus(): void;
}
// 要实现多个接口,我们只需要中间用 逗号 隔开即可。
class Cellphone implements Radio, Battery {
switchRadio() {
}
checkBatteryStatus() {
}
}
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function echo(arg) {
return arg
}
const result = echo(123)
// 这时候我们发现了一个问题,我们传入了数字,但是返回了 any
function echo<T>(arg: T): T {
return arg
}
const result = echo(123)
// 泛型也可以传入多个值
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
const result = swap(['string', 123])
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
function echoWithArr<T>(arg: T): T {
console.log(arg.length)
return arg
}
// 上例中,泛型 T 不一定包含属性 length,我们可以给他传入任意类型,
//当然有些不包括 length 属性,那样就会报错
interface IWithLength {
length: number;
}
function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}
echoWithLength('str')
const result3 = echoWithLength({length: 10})
const result4 = echoWithLength([1, 2, 3])
泛型与类和接口
class Queue {
private data = [];
push(item) {
return this.data.push(item)
}
pop() {
return this.data.shift()
}
}
const queue = new Queue()
queue.push(1)
queue.push('str')
console.log(queue.pop().toFixed())
console.log(queue.pop().toFixed())
//在上述代码中存在一个问题,它允许你向队列中添加任何类型的数据,
//当然,当数据被弹出队列时,也可以是任意类型。
//在上面的示例中,看起来人们可以向队列中添加string 类型的数据,
//但是那么在使用的过程中,就会出现我们无法捕捉到的错误,
class Queue<T> {
private data = [];
push(item: T) {
return this.data.push(item)
}
pop(): T {
return this.data.shift()
}
}
const queue = new Queue<number>()
//泛型和 interface
interface KeyPair<T, U> {
key: T;
value: U;
}
let kp1: KeyPair<number, string> = { key: 1, value: "str"}
let kp2: KeyPair<string, number> = { key: "str", value: 123}
泛型的好处
- 函数和类可以轻松地支持多种类型,增强程序的扩展性
- 不必写冗长的联合类型,增强代码的可读性
- 灵活控制类型之间的约束
类型声明
声明文件
declare
当使用第三方库时,很多三方库不是用 TS 写的,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
.d.ts
通常我们会把声明语句放到一个单独的文件(Vue.d.ts)中,这就是声明文件,以 .d.ts 为后缀。
一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件
第三方库
社区使用 @types 统一管理第三方库的声明文件
npm install @types/lodash -D
内置类型
https://github.com/Microsoft/TypeScript/tree/main/src/lib
const a: Array<number> = [1,2,3]
// 大家可以看到这个类型,不同的文件中有多处定义,但是它们都是 内部定义的一部分,
然后根据不同的版本或者功能合并在了一起,一个interface 或者 类多次定义会合并在一起
。这些文件一般都是以 lib 开头,以 d.ts 结尾,告诉大家,我是一个内置对象类型欧
const date: Date = new Date()
const reg = /abc/
// 我们还可以使用一些 build in object,内置对象,
比如 Math 与其他全局对象不同的是,Math 不是一个构造器。Math 的所有属性与方法都是静态的。
Math.pow(2,2)
// DOM 和 BOM 标准对象
// document 对象,返回的是一个 HTMLElement
let body: HTMLElement = document.body
// document 上面的query 方法,返回的是一个 nodeList 类型
let allLis = document.querySelectorAll('li')
//当然添加事件也是很重要的一部分,document 上面有 addEventListener 方法,
注意这个回调函数,因为类型推断,这里面的 e 事件对象也自动获得了类型,
这里是个 mouseEvent 类型,因为点击是一个鼠标事件,现在我们可以方便的使用 e 上面的方法和属性。
document.addEventListener('click', (e) => {
e.preventDefault()
})
Typescript 还提供了一些功能性,帮助性的类型,这些类型,大家在 js 的世界是看不到的,这些类型叫做 utility types,提供一些简洁明快而且非常方便的功能。
// partial,它可以把传入的类型都变成可选
interface IPerson {
name: string
age: number
}
let viking: IPerson = { name: 'viking', age: 20 }
type IPartial = Partial<IPerson>
let viking2: IPartial = { }
// Omit,它返回的类型可以忽略传入类型的某个属性
type IOmit = Omit<IPerson, 'name'>
let viking3: IOmit = { age: 20 }