TypeScript类型系统的使用笔记,可做参考建议快速入门

550 阅读6分钟

学习typeScript的前提

知道typeScript的优缺点,就知道自己是不是要学习typeScript

优点:

  1. 避免开发过程中有可能会出现的类型异常,提高代码的执行效率以及代码的可靠程度,
  2. 支持自动转换es6新特性,最低编译至es3
  3. 任何一种javaScript运行环境都支持(浏览器,桌面端 服务器node)都可以转化
  4. 生态特别健全,完善。我们前端使用的vscode有很多的类型提示,使用特别流畅,提高开发效率
  5. 属于「渐进式的」,不用全部学会ts的所有知识,学一个用一个,可以兼容js的代码,降低使用门槛 缺点:
  6. 相对js不够灵活自由
  7. 语言本省多了很多概念(例如接口, 泛型, 枚举)
  8. 在周期较短或者较小项目初期,TypeScript会增加一些成本,有很多的类型和接口声明(当然如果是长期维护大型的项目的话,这些成本就不算什么一劳永逸)

如果你觉得你在工作中降回面临长期维护大型页面系统,也想使用es6+的较新的特性,vscode有更好的提示提升你的开发效率的考量,那就很适合学习TypeScript。不灵活 和 多出概念的学习成本,前期的类型和接口的声明时间成本的相对而言微不足道的。而且可以渐进式使用,不用全部学会,学一个可以用一个。对很多平台的js运行环境完全兼容。

原始类型

原始类型声明 首字母小写

// 原始类型可以默认为空 严格模式不允许
const a: number = 6 // NaN Infinity

const a1: string = 'hello typescript'

const a2: boolean = true // false

const a3: undefined = undefined

const a4: null = null

const a5: void = undefined // 严格模式只能为 undefined 非严格还可以是 null

// 如果target设置为es5 es6新增加的内容都会报错
const a6: symbol = Symbol('小米')

const a7: void =  undefined

中文错误消息

// yarn tsc --locale zh-CN

vs code也可以配置 搜索typescript locale 修改成 zh-CN 错误提示就会变成中文的

作用域问题

将ts声明变量模块化

  1. 可以把代码放入立即执行函数里面 void function() {}()

  2. 可以使用export 这样所有的变量就是在这个模块内了 export{}

在工作中可能都不会察觉到,但是我们要知道这个原因是什么

Object类型

变量定义为 object时 值可以是 function [] {} 都是可以的

  1. object类型,他代表原始类型以外的类型,对象 {}, 函数 function 数组[]

  2. 我们也可以定义一个对象有固定的字段属性,如果超出属性或者属性名在声明里面没有就报错。对象的声明可以是 {name: string,age: number} 但是推荐的还是interface

  // 多一个属性就会报错
  const person: {name: string, age: number} = {name: 'ali', age: 20}
  // 最好使用接口声明
  interface IPerson {
    name: string,
    age:number
  }
  const person: IPerson = {name: 'ali', age: 20}

数组类型

定义的方式有两种:

  • Array
  • number[] // 下面我们来看一下强类型的好处
function sum(...args) {
  // 这里判断是不是每个成员都是数字
  return args.reduce((prev, current) => prev + current,0)
}
function sum(...args: number[]) {
  // 就不用判断了
  return args.reduce((prev, current) => prev + current,0)
}

// 如果是JavaScript我们还需判断 这个数组里面每一个元素是不是number 不是的话就不能参与计算,如果参数有类型声明就不需要这个判断了

元组类型

这个类型是定义一个数组且长度固定每个元素的类型也是固定的

	const tuple: [string, number] = ['zlx', 100]

    // const age = tuple[0]
    // const name = tuple[1]

    const [age, name] = tuple
    // ---------------

    // 元组 比较常见的 在react中useState就是一种
    // 还有 Object.entries({foo: 'bar'})

枚举类型

都是存在固定的几个值 使用枚举更具语义化 上面使用都会产生双向枚举对象 可以使用索引访问状态 也可以用状态获取索引值 如果一般不适用索引获取状态值得话建议使用常量枚举在声明前加const 即可 tsc 编译代码就会有所变化 // 第二种 字符串型

// 第二种 字符串型
enum PostStatus2 {
  Draft = 'aaa',
  Unpublished = 'bbb',
  Published = 'rrr',
}
const post = {
  title: 'Hello TypeScript',
  content: 'TypeScript is a typed superset of javascript',
  status: PostStatus2.Draft,
}
// 上面使用都会产生双向枚举对象 可以使用索引访问状态 也可以用状态获取索引值 如果一般不适用索引获取状态值得话建议使用常量枚举在声明前加const 即可
// tsc 编译代码就会有所变化
const enum PostStatus3 {
  Draft,
  Unpublished,
  Published,
}
const post1 = {
  title: 'Hello TypeScript',
  content: 'TypeScript is a typed superset of javascript',
  status: PostStatus3.Draft,
}

// 编译之后
var PostStatus2;
(function (PostStatus2) {
    PostStatus2["Draft"] = "aaa";
    PostStatus2["Unpublished"] = "bbb";
    PostStatus2["Published"] = "rrr";
})(PostStatus2 || (PostStatus2 = {}));
var post = {
    title: 'Hello TypeScript',
    content: 'TypeScript is a typed superset of javascript',
    status: PostStatus2.Draft,
};
var post1 = {
    title: 'Hello TypeScript',
    content: 'TypeScript is a typed superset of javascript',
    status: 0 /* Draft */,
};

函数类型

  • 对函数输入输出数据类型进行控制
  • 可选参数和有默认值的参数,都必须写在参数列表最后。这样的原因也很简单,js的参数传递都是通过位置进行传递的,如果是默认和可选参数都可以不传,不传默认的就帮你补上了,可选参数不传也不影响对错。
  • 如果传任意参数就可以用剩余参数表达式...rest

任意类型

为any 对参数或者变量都不进行类型检查,为任何值都不报错。

最好不要使用这个类型,不然ts类型系统就是去了意义,但是也有我们在兼容老代码的时候,不得用这个any类型保持兼容

隐式类型推断(Type Inference)

如果你对标识符没有进行类型注解,ts会针对你使用情况进行值类型推导。这个特性就是隐式类型推断。(使用者无感知)

建议每个变量都添加明确的类型,这样更有利于类型的推导 减少代码的执行错误。

类型断言 (type Assertions)

在特殊情况下,typeScript无法推断出变量的具体类型,我们强制指定一个变量的类型。 是在编译阶段就指定变量的类型,而不是运行阶段。类型转换在运行时 有两种可以进行类型断言

  • 可以使用as关键值
  const nums = [110, 120, 119, 112]
  const res = nums.find(i => i > 0)
  const num1 = res as number // 可以告诉ts编译器 这个变量的值一定是number
  • 使用 <> 进行断言 // 在JSX 下不能使用

接口 (interfaces)

我们可以理解为是一种规范一种契约。是一种抽象的概念,用来去约定对象的结构。使用这种接口就要全部遵循这个接口的全部给规定

比较直观的就是, 约定一个对象当中应该有哪些成员而且可以约定这些成员是什么类型 建议使用分号分隔

interface IPost {
  title: string;
  content: string;
}
function printPost(post: IPost) {
  console.log(post.title)
  console.log(post.content)
}
printPost({
  title: 'Hello TypeScript',
  content: 'A javascript superset'
})

定义可选成员,只读成员

可选成员: 成员后面加问号 只读成员: 在变量前面使用关键字 readonly

interface Post {
  title: string;
  content: string;
  subTitle?: string; // 可选参数
  readonly summary: string;
}

类 (Classes)

描述一类具体事务的抽象特征

比如手机就是一种类型

用来描述一类具体对象的抽象成员

es6以前,函数 + 原型模拟实现类,es6开始JavaScript中有了专门的class

typeScript 增强了class的相关语法

  1. 一定在类中指定类成员 使用冒号定义成员类型 或者 赋值初始值
class Person {
  // name: string; // init name
  // 可以定义类型初始化
  age: number;
  // 也可以赋值初始化
  name = '美团'
  constructor(name: string, age: number) {
    this.name = name;
    this.age  = age;
  }
}
  1. 访问修饰符
  • 类访问修饰符默认是public,可以被外部访问,建议加上这样代码更容易理解
  • 如果设置属性为 private的话,外部访问该变量会报错
  • protected 为受保护属性,只允许在子类当中被访问
class Person {
  // name: string; // init name
  // 可以定义类型初始化
  // 使用private,当外部访问的时候就会报错
  private age: number;
  // 也可以赋值初始化
  public name = '美团'
  constructor(name: string, age: number) {
    this.name = name;
    this.age  = age;
  }
}

同时构造函数也是可以有访问修饰符的,当构造函数有私有private的时候,就不允许外部创建该类的实力 但是我们可以使用static的方法 定义create来new该实力 Object.create()大家应该很熟悉吧

class Student extends Person {
  // private 修饰构造器的类 外部无法创建
  private constructor(name: string, age: number) {
    super(name, age);
    console.log(this.gender);
    this.gender = false;
  }
  // 可以使用类的静态方法来实现
  static create(name: string, age: number) {
    return new Student(name, age)
  }
}

const tom = new Person('谷歌', 18)
console.log(tom.name)
// console.log(tom.age)
// console.log(tom.gender)
// 无法使用new 创建student对象
// const jack = new Student()
const jack = Student.create('jack', 20)

类的只读性

readonly 给属性设置只读的,使用的时候一定要放在访问修饰符的后面

class Person {
  protected readonly gender: boolean
}

接口

手机是一个类型,但是市面上所有的手机呢是可以打电话,发短信的,因为手机的特征就是这些。在以前呢座机呢也有打电话的能力。那不同的类与类之间也有共同的特征。这些特征我们就可以抽象为接口

interface Eat {
  eat(food: string): void
}

interface Run {
  eat(distance: string): void
}

class Person implements Eat, Run {
  eat(food: string): void {
    console.log(`优雅的进餐: ${food}`)
  }
  run (distance: string): void {
    console.log(`直立行走:${distance}`)
  }
}

class Animal implements Eat, Run {
  eat(food: string): void {
    console.log(`呼噜呼噜的吃: ${food}`)
  }
  run (distance: string): void {
    console.log(`爬行: ${distance}`)
  }
}

像java 和 c#这样的面向对象语言呢建议接口最好更加的喜欢更加的简单,一个接口最好只有一个能力

我们要学会用接口去对类进行一些能力的抽象。 所以看到这里如果知道java的c#的话能够更好地抽象接口,所以说最好不要局限自己,一定多多开阔自己技术事业,的学习其特定的语言,让自己的知识更加的系统更加的全面。只了解javascript的人就算是精通也设计不出来很厉害产品,比如现在流行的框架就是MVVM最开始就在其他语言出现的

抽象类

抽象类和接口有点类似,与接口不同的是抽象类包含一些具体的实现,那一般比较大的类我们都会去使用抽象类实现,像动物这个一般就是泛指,没有具体表现,我们就可以用抽象类实现

抽象类还是要好好了解他的思想,不断的去熟悉,使用上并没有很大的难度

泛型

在我们使用函数 接口 或者类的时候我们没有特意指定相关类型,等到使用的时候再去指定具体的特征。这样的目的就是极大程度的复用我们的代码

简单来说,在定义函数时我们不能明确的类型编程一个参数。让我们使用的时候再去传递一个类型参数

  function createArray<T>(length: number, value: T): T {
    const arr = Array<T>(length).fill(value)
    return arr
  }
  const res = createArray<string>(3, 'foo')

类型声明

在项目开发当中我们会使用一些通过npm安装的第三方模块,而这些模块没有使用typescript进行开发,所以他提供的对象成员呢就不会有很好的强类型体验。

拿lodash举例,如果使用一个函数,当我们使用vscode时候这个函数没有很好的输入参数类型提示也没有输出的内容的类型提示。 ts可以使用类型声明的语法进行优化

import {camelCase} from 'lodash'

declare function camelCase(input: string): string

const res = camelCase('hello typed')

为了兼容没有ts开发的模块,ts社区非常的强大,现在很多npm常用的模块都帮忙写了相应的类型声明,我们只需要安装一下相应的npm模块的声明即可

结束: 希望对读者有所帮助。。。