[TOC]

原始数据类型

js的数据类型分为两种: 原始数据类型和对象类型(本笔记中所有的知识点会对照js进行学习)

原始数据类型包括: 布尔值,数值,字符串,null,undefined,以及es6中的 symbol和 es10中的 bigint

布尔值

ts中,使用 boolean 来定义布尔值类型

let isDone: boolean = false

注意,使用构造函数 Boolean创建的对象不是布尔值

let isDone: boolean = new Boolean(1) // 其实返回的是一个Boolean对象,Boolean{true}

在ts中,boolean是js中的基本类型,但是Boolean是js中的构造函数,其他基本类型(除了 null 和 undefined)也是

数值

ts中,使用 number 定义数值类型

let count: number = 5
let countX: number = 0xf00d
let countN: number = NaN
let infintyNumber: number = Infinity

// 以上定义编译之后会被编译成十进制数

字符串

ts中使用 string 来定义字符串类型

let myName: string = 'Tom'
let myAge: number = 22

let sentence: string = `Hello, myName is ${myName}, i'm ${myAge}.`

空值

ts中,没有空值的概念,可以用 void 来表示没有任何返回值的函数

function alertName(): viod {
    alert('my name is Tom')
}
// ts中,定义一个void的变量没有意义,因为他只能被赋值为 undefined 和 null

null, undefined

ts中, 使用 null 和 undefined 来定义这两个原始数据类型

let u: null = null
let n: undefined = undefined

与void不同的是,undefined和null是所有类型的子类型,也就是说,undefined类型的变量,可以赋值给number类型

let num: number = undefined // 不会报错
let unmN: numebr = null 

let u:void 
let numU: numebr = u  // 会报错

任意值

ts中, any用来表示允许赋值为任意类型

什么是任意类型?
如果是一个普通类型,在赋值过程中改变类型是不被允许的,但是如果是any类型,可以被赋值为任何类型

let count: string = 'seven'
count = 7 // bad

let count: any = 'seven'
count = 7 // good

在任意值上访问任何属性和方法都是允许的

let anything: any = 'hello'
anything.myName
anything.myThing.myName

anything.setName()
anything.getName().sayHello()

// 可以认为,一个变量为任意值之后,对他的任何操作,返回的内容类型都是任意值

未声明类型的变量

在ts中,如果变量声明的时候没有指定其类型,那么它会被识别成any类型

let something
something = 7
something = [1,3,4]

// any类型感觉就是回到了js模式

类型推论

如果没有明确的指定类型,那么ts会一招一个规则来推断出这个类型

// 以下没有指定类型,但是会编译出错
let number = 6
numbner = 'six'

// 等价于
let number: number = 6
number = 'six'

如果声明的时候没有赋值,就会推断成any类型,参考上一小节


联合类型

ts中,联合类型表示取值可以为多种类型中的一种

联合类型使用 | 分隔不同的类型

let myNumber: number | string
myNumber = 7
myNumber = 'six'

对象类型

在ts中,使用接口(interfaces)来定义对象类型

什么是接口?
在面向对象语言中,接口是一个很重要的概念,他是对行为的抽象,而具体如果行动需要类去实现

interface Person() {
    name: string;
  age: number;
}
let tom: Person = {
    name: 'Tom',
  age: 22,
}

定义的接口一般首字母大写,并且注意,定义变量比接口少一些属性或者多一些属性是不允许的
如果我们希望不要完全匹配一个接口,那么可以用可选属性
但是仍然不能添加没有定义的属性

interface Person{
    name: string;
  age?: number;
}
// good
let tom: Person = {
    name: 'Tom'
}

有时候我们希望一个接口允许有任意的属性

interface Person{
    name: string;
  age?: number;
  [propName: string]: any
}
let tom: Person = {
    name: 'Tom',
  sex: '男'
}

注意,一旦定义了任意属性,那么其他的确定属性和可选类型必须是他类型的子集

interface Person{
    name: string;
  age?: number;
  [propName: string]: string
}
let tom: Person = {
    name: 'Tom',
  age: 12,
  sex: '男
}
// 报错,因为number不是string的子集

如果想在一个接口中定义多个类型的属性,那么需要使用联合属性

interface Person{
    name: string;
  age?: number;
  [propName: string]: string | number
}
let tom: Person = {
    name: 'Tom',
  age: 14,
  sex: '男'
}

只读属性,我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用readonly来定义只读属性

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;  // 报错,因为对象初始化的时候已经被赋值了

注意,只读属性约束在第一次给对象赋值的时候,也就是对象初始化的时候,并不是第一次给对象赋值的时候


数组类型

在ts中,数组类型有很多种定义方式,比较灵活

  1. 【类型 + 方括号】表示法

最简单的方法之一

let numArray: number[] = [1,2,3,4]
  1. 数组泛型表示法

    let numArray: Array<number> = [1,2,3,5]
    
  2. 接口数组表示法,一般不推荐这么做 ```typescript interface NumArray {

[index: number]: number;

} let numArray: NumArray = [1,2,3,4]


4. 类数组(不是数组类型,比如arguments)
```typescript
function sum() {
    let args: number[] = arguments;
}
// 报错,arguments是一个类数组,不能用普通的数组定义方式,需要用接口

function sum() {
    let args: {
      [index: number]: number;
    length: number;
    callee: Function;
  } = arguments
}

上面这个例子中,我们除了约束当索引是数字时,值必须是数字,还约束了他的length和callee两个属性

Any在数组中的应用,等于回到js模式

let numArray: any[] = [1, '2', {a: 1}]

函数的类型

在ts中,有两种常见的函数定义方式——函数声明(Function Declaration)和函数表达式(Function Expression)

// 函数声明
function sum(x, y) {
    return x + y;
}

// 函数表达式
let mySum = function(x, y) {
    return x + y;
}

函数的输入和输出,需要对其进行约束,要把输入和输出同时考虑到,其中函数式声明如下:

function sum(x: number, y: number) {
    return x + y;
}

函数表达式声明如下:

let mySum = function (x: number, y: number) {
    return x + y;
}
// 错误,因为支队等号右侧的匿名函数进行了类型定义,左侧的mySum也需要定义类型

let mySum: (x: number, y: number) => number = function (x: number, y: number) {
    return x + y;
}
//  => 左侧括号内是输入类型,右侧是输出类型

注意这里的 => 和 es6中的箭头函数不是一个东西

还可以通过定义接口的方式来定义一个函数

interface SearchFunc {
    (source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function (source: string, subString: string) {
    return source.search(subSrting) !== -1;
}

可选参数

输入多余的,或者少于要求的都是不允许的,那么如何定义可选的参数呢,可以用到 ?

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
      return firstName + lastName
  }
  return firstName
}
let tomcat = buildName('Tom')

有时候我们需要函数中参数带上默认值,下面对比列出js和ts的写法

// js
const buildName = (firstName = 'Tom', lastName = 'Cat' ) => {
    return firstName + lastName
}
// ts
function buildName(firstName: string = 'Tom', lastName: string = 'Cat') {
    return firstName + lastName
}

剩余参数

es6中,可以用 … 来获取函数中的剩余参数

function push(array, ...items) {
    items.forEach(function(item) {
      array.push(item)
  })
}

items是一个数组,所以在ts中可以这样定义

function push(array: any[], ...item: any[]) {
    items.forEach(item => {
      array.push(item)
  })
}
let a = []
let b = [1,2,3]

push(a, ...b)

重载

重载允许一个函数接受不同数量或者类型的参数时,作不同的处理

如:实现一个函数,输入数字或字符串,反转数字或者字符串

function reverse(x: number | string) : number | string | void {
    if (typeof x === 'number') {
      return Number(x.toString().split('').reverse().join(''))
  }
  else if (typeof x === 'string') {
      return x.split('').reverse().join('')
  }
}

上面这个定义无法精准的表达出,输入为数字,输出也要是数字,输入为字符串,输出也要是字符串,可以通过下面这种多次定义方式

function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string) : number | string | void {
    if (typeof x === 'number') {
      return Number(x.toString().split('').reverse().join(''))
  }
  else if (typeof x === 'string') {
      return x.split('').reverse().join('')
  }
}

ts会优先从前面的函数进行匹配,所以在定义的时候需要把精确的定义写在前面


类型断言

类型断言是一个难点,它可以手动来指定一个值的类型,需要反复理解

语法

值 as 类型

<类型>值

tsx语法不支持第二种,所有使用的时候暂时只使用第一种

为什么要用类型断言?

  1. 将一个联合类型断言为其中一个类型

ts中,如果不确定一个联合类型的变量到底属于那个类型的时候,我们只能访问公共的属性或者方法

interface Cat {
    name: string;
  run(): void;
}
interface Fish{
    name: string;
  swim(): void;
}
function getName(animal: Cat | Fish) {
    return animal.name;
}

如果我们在不确定是哪种类型的情况下想要访问其中一个类型特有的方法和属性,就可以使用类型断言

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}
function isFish(animal: Cat | Fish) {
    if (typeof (animal as Fish).swim === 'function') {
      return true
  }
  return false
}

值得注意的是,类型断言只能欺骗编译器,但是不能滥用

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function swim(animal: Cat | Fish) {
    (animal as Fish).swim();
}

const tom: Cat = {
    name: 'Tom',
    run() { console.log('run') }
};
swim(tom);

上面的例子中,tom是cat类型,是没有swim方法的

  1. 将一个父类断言成更加具体的子类的时候

当类之间有继承,类型断言也十分常见:

calss ApiError extends Error {
    code: number = 0;
}
clasee HttpError extends Error {
    stateCode: number = 200
}
function isApiError(error: Error) {
    if (typeof (error as ApiError).code === 'number') {
      return true
  }
  return false
}

上面的例子中,我们声明了一盒函数来判断是否是apiError,所以传入参数的类型是比较抽象的父类,这样就可以接受error或者他的子类来作为参数
又因为父类中没有code 属性,所有直接获取error.code会报错,所有使用断言可以避免这个问题,我们也可以使用 js 的 instanceof 来判断error是否是 apiError 的实例

但是如果是一个ts的接口,无法通过 instanceof 来判断,就只能用类型断言来判断了

interface ApiError extends Error {
    code: number;
}
interface HttpError extends Error {
    statusCode: number;
}

function isApiError(error: Error) {
    if (error instanceof ApiError) {
        return true;
    }
    return false;
}
// 报错

function isApiError(error: Error) {
    if (typeof (error as ApiError).code === 'number') {
      return true
  }
  return false
}
// 正确
  1. 将任意一个类型断言成 any

ts的每个值得类型都具体,精准,因此可能有以下几种情况

const foo: number = 1
foo.length = 1 // 报错

window.foo = 1 // 报错
(window as any).foo = 1 // good

使用 as any 将 window 断言成 any 类型,访问任何变量都是允许的

  1. 将 any 类型断言成一个具体的类型

开发中,如果我们遇到其他开发者编写的代码,存在滥用 any 类型的情况,那么需要通过类型断言及时将 any 类型转换为精确类型,使代码维护性更高

function getCacheData(key: string) {
    reutrn (windwo as any).cache[key]
}

interface Cat {
    name: string;
  run(): void;
}
const tom = getCacheData('Tom') as Cat
tom.run()

上面的例子中,我们调用完了getCacheData之后,将他断言成Cat类型,就明确了tom的类型

声明文件

当使用第三方库时候,在ts中,需要引用他的声明文件,才能获取对应代码的不全,接口提示功能

什么是声明语句?
举个例子,在js中,如果我们想使用第三方库JQuery,那么直接在html中通过