TypeScript -- JS的超集

1,309 阅读11分钟

TypeScript官方文档

看完本篇文章,自己组织语言回答以下几个问题:

typeScript相关:

  1. TypeScript是什么?
  2. TypeScript 类型系统的主要优势?
  3. TS 和 JS的区别是什么?

泛型相关:

  1. 泛型是什么?
  2. 我们为什么需要用泛型?
  3. 使用泛型典型的应用场景?

> TypeScript类型概述

类型注解:为变量添加类型约束。约定什么类型,就只能给变量赋值该类型的值,不然就会报错。

可以将 TS 中的常用基础类型细分为两类:

  • JS 已有类型
    • 原始类型,简单类型(number/string/boolean/null/undefined
    • 复杂数据类型(数组,对象,函数等)
  • TS 新增加粗样式类型
    • 联合类型
    • 自定义类型(类型别名
    • 接口
    • 元组
    • 字面量类型
    • 枚举
    • void
    • ...

1、简单数据类型

let num: number = 18
let str: string = '老师'
let bool: boolean = false
等等...

2、数组类型

// 写法一: 类型[] = [值...]
let arr1: number[] = [1, 3, 5]

// 写法二:Array<类型> = [值.....]
let arr2: Array<string> = ['a', 'b', 'c']

3、联合类型( | )

应用场景:当一个变量需要支持多种类型的赋值时,由两个或多个其他类型组成联合类型,可以是这些类型中的任意一种

// 1. 联合基本类型

let a: number | string = 1
    a = '1' // 可以给 a 赋数字和字符串类型
// 2. 联合数组类型
let arr: (number | string)[] = [1, 2, '3'] // 可以放数字和字符串的数组

4、类型别名(type)

应用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用

// 用法: type 别名(推荐首字母大写) = 类型
type Mytype = string | number | boolean // 可以是字符串、数字和布尔值类型

let a: Mytype = 1
    a = 'a'
    a = true
    
let b: Mytype = 1
......

5、函数类型

函数的类型实际上指的是:函数参数返回值

//基本使用
  // 1. 普通函数 
  // function 函数名(形参1: 类型=默认值, 形参2:类型=默认值): 返回值类型 { }  
  function fn1(a: number, b:number=100):number {
    return a + b
  }
  let num = fn1(1, 2)
  console.log(num); // 3
  

  // 2. 箭头函数 
  // const 函数名(形参1: 类型=默认值, 形参2:类型=默认值):返回值类型 => { }
  const fn2 = (a: number, b:number=100):number => {
    return a + b
  }
  let num2 = fn2(1) // 第二值是默认值为100 可传可不传
  console.log(num2); // 101
  

  // 3. 函数-类型 type
  type MyFn = (a: number, b: number) => number 
  const add: MyFn = (a, b) =>{return a+b}
  const sub: MyFn = (x, y) =>{return x+y}
  let addNum = add(1, 2)
  console.log(addNum);
 
  • void 类型:如果一个函数没有返回值,此时在 TS 的类型中,应该使用 void 类型
// 函数返回值为 void -- 如果函数没有返回值
  /** 三种情况
   * 1 不写return
   * 2 return
   * 3 return undefined
   */
  function fn3() :void {
    console.log('fn3');
  }
  const fn4 = ():void =>{}
  // 自定义函数别名
  type MyFnVoid = (a:number, b:number) => void 

  • 可选参数(?):使用函数实现某个功能时,参数可以传也可以不传,在给函数参数指定类型时,就用到可选参数了
// 可选参数
  // 在参数名的后面添加 ? 代表它可写可不写
  function fn5(a?:number, b?:number) {
    console.log(a, b);
  }
  fn5(1, 2)
  fn5(1)
  fn5()

6、对象类型

JS 中的对象是由属性和方法构成的,而 TS 对象的类型就是在描述对象的结构(有什么类型的属性和方法)

 // 1. 基本使用
  // 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
  let obj:{ name: string; age: number } = { name: 'xiaxia', age: 18 }


 // 2. 使用类型别名
  type MyStlye = {
    // 通过换行来分隔多个属性类型,可以去掉 `;`
    name: string
    age: number
    // 无参数 无返回值
    sayHi: () => void
    // 参数为number 返回值为string
    Hello(count: number):string
  }
  let obj1: MyStlye = {
    name: 'xiaxia',
    age: 18,
    sayHi() {},
    Hello(count: 1) { return '1' }
  }  

7、接口函数(interface)

应用场景:当一个 对象类型 被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的

  • 接口继承--- 如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
// 接口用法: 类型名称:Ixxx
  interface IStudent {
    name: string
    age: number
    sayHi: () => void
  }
  let stu1: IStudent = {
    name: 'xiaxia',
    age: 18,
    sayHi(){}
  }


  // 接口继承 - 公共的属性或方法抽离出来,通过继承来实现复用
  interface IPoint2D { x: number; y: number }
  const p1: IPoint2D = { x:1, y:2 }
  // interface IPoint3D { x: number; y: number; z: number }
  // 这里使用继承-简化
  interface IPoint3D extends IPoint2D { z: number }
  const p2: IPoint3D = { x:1, y:2, z:3 }


  /** interface vs type
   * 区别1: type-类型别名,只能为对象指定类型; 
   *        interface-接口,不仅可以为对象指定类型,实际上可以为任意类型指定别名
   * 区别2: type-类型别名 不可以继承
   *        interface-接口 可以继承
  */

8、元组类型(Tuple)

  • 概述:一种特殊的数组,约定了元素的个数和类型
  • 应用场景:经纬度表示地址
  // 元组 Tuple
  // 需求: 一个数组 两个元素,都是number类型
  const positon:[number, number] = [1, 2]

9、字面量类型

  • 字面量:{} [] 18 'abc' false function(){}
  let str1 = 'Hello TS'
  let str2 = 'Hello TS'
  
  // 1. str1是什么类型? string类型,必须只能保存字符串
  // 2. str2是什么类型? 'Hello TS'字面量类型,只能保存Hello TS
  
  // 可以改写成下面的类型
  let str1: string = 'Hello TS'
  let str2: 'Hello TS' = 'Hello TS'

  // 字面量类型 - 单独使用没有应用场景
  // 字面量类型一般和联合类型一起使用
  type Direction = 'up' | 'down' | 'left' | 'right'

10、枚举(enum)

使用:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个

  // 1. 定义枚举 - 大写字母开头
  enum Direction { Up, Down, Left, Right }
  console.log(Direction);
  // {
  //   '0': 'Up',
  //   '1': 'Down',
  //   '2': 'Left',
  //   '3': 'Right',
  //   Up: 0,
  //   Down: 1,
  //   Left: 2,
  //   Right: 3
  // }

  // 2. 使用枚举  枚举名字.xxxx
  const d2:Direction = Direction.Up
  function fn(a: Direction) {
    if(a === Direction.Left){

    }
  }
  

  // 3. 场景: 后端给的性别是 0(男) 1(女)
  enum Gebder { 'man', 'woman' }
  const p1:Gebder = Gebder.man // 0
  const p2:Gebder = Gebder.woman // 1
  • 一个小拓展

其他的类型会在编译为 JS 代码时自动移除。但是,枚举类型会被编译为 JS 代码,TS编译成JS的代码对照:

// TS
enum Direction { Up, Down, Left, Right }

// JS
// 将这个枚举转换成js后
var Direction = void 0;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

11、any类型

应用场景:向后端发送axios,当不知道返回的值是什么类型时,可以先定义为any,但ts的原则不推荐使用any

let obj: any = { x: 0 }

obj.bar = 100
obj()
const n: number = obj

12、类型推论

// 变量 age 的类型被自动推断为:number
// let age: number = 18
let age = 18

// 函数返回值的类型被自动推断为:number
function add(num1: number, num2: number) {
  return num1 + num2
}

13、类型断言(as)

const aLink = document.getElementById('link')

const aLink = document.getElementById('link') as HTMLAnchorElement

泛型<>

泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用

本质: 参数化类型,通俗的讲,就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和函数的创建中,分别成为泛型类泛型接口泛型函数。可以通过以下代码理解泛型的本质

// 创建一个 函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)
function fn(value: number): number { return value }

1. 泛型函数

语法: 在函数名称的后面添加 <>(尖括号),尖括号中指定具体的类型

  // 泛型函数 ====> 定义一个带泛型变量的函数
  // 类型变量 T,是一种特殊类型的变量,它处理类型而不是值
  function id<T>(value:T) : T{
    return value
  }
  // 1.1 使用: 尖括号中指定具体的类型
  const r1 = id<number>(1)
  const r2 = id<boolean>(false)
  // 1.2 简化-类型推断
  const r3 = id('abc') // String
  
  // 这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全

2. 泛型约束

为什么需要泛型约束?

答: 默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性,因为其无法保证传入的类型一定存在某个属性。 比如 length 属性,number 类型就没有 length,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)

添加泛型约束的方法: 1 指定更加具体的类型 ;2 添加约束。

2.1 指定更加具体的类型

// 我们这里使用方法也是拿lenght属性来举例

// 将类型修改为 Type[](Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
function id<Type>(value: Type[]): Type[] {
  console.log(value.length)
  return value
}

2.2 添加约束

// 我们这里使用方法也是拿lenght属性来举例

// 创建一个接口
interface ILength { length: number }

// Type extends ILength 添加泛型约束
// 该约束表示:传入的类型必须具有 length 属性。比如数组
function id<Type extends ILength>(value: Type): Type {
  console.log(value.length)
  return value
}

3. 泛型类型变量

  • 可以有多个
  • 并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
  // 多个类型变量 - 相互约束
  // keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
  // 比如: 第二个类型受第一个变量的约束 Key extends keyof Type 代表继承 keyof Type
  // keyof Type 是 Type的所有 key值
  
  let obj = { name: 'jack', age: 18 }
  // getProp( 对象, 属性名 ) ===> 属性值
  // 本示例中 keyof Type 实际上获取的是 obj 对象所有键的联合类型,也就是:'name' | 'age'
  function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key]
  }
  getProp(obj, 'name') 
  // 因为 obj 是一个对象,所以这里的 Type 类型为对象类型

4. 泛型接口

  interface IdFunc<Type> {
    id: (value: Type) => Type
    ids: () => Type[]
  }
  let obj: IdFunc<number> = {
    id(value) { return value },
    ids() { return [1, 3, 5] }
  }

  // 场景: 定义数组类型时
  // 方法一:
  let arr1: number[] = [1, 2, 3]
  // 方法二:
  // 这里的 Array 就是泛型接口
  let arr2: Array<string> = ['1', '2', '3']

5. 泛型练习

  • 最后来一个泛型小练习检测一下泛型学习的成果: 下面是题目要求以及要改写的代码
// 1. 题目 - useState它接收一个任意类型的数据,返回一个数组。
/** 要求:
 *  数组的第一个元素的类型与入参一致; 
 *  数组的第二个元素是一个函数
 *  第二个元素函数的入参类型和返回值类型与useState的入参一致 
 */

// 2. 将以下的代码改写成符合上述要求的 Ts 代码
function useState(value) {
  const setValue = () => {
  }
  return [value, setValue]
} 
// 测试代码 - 鼠标悬停查看结果
const [num, setNum] = useState(0)
const [str, setStr] = useState('ab')
  • 下面是上述代码的答案:自己思考后再看答案,鼠标悬停在测试代码上可以看到符合题目要求的结果
function useState<Type>(value:Type): [Type, (val: Type)=> Type] {
  const setValue = (val: Type): Type => {
    return val
  }
  return [value, setValue]
}
// 测试代码 - 鼠标悬停查看结果
const [str, setStr] = useState('123')
const [num, setNum] = useState(123)

总结

本人回答一下文章开头的几个问题,有不同意见的小伙伴可以在评论中发表自己的意见

1. TypeScript是什么?

typescript是微软公司开源的编程语言, type是类型的意思,表示在js的基础之上额外增加了类型系统、提供了类型支持,是js的超集

2. TypeScript 类型系统的主要优势?

可以显示标记出代码中的意外行为,从而降低了发生错误的可能性

3. TS 和 JS的区别是什么?

TypeScript = Type + JavaScript(在 JS 基础之上,添加了类型支持)

从编程语言的动静来区分:

  • TypeScript 属于静态类型的编程语言,编译期做类型检查

  • JavaScript 属于动态类型的编程语言,执行期做类型检查

4. TypeScript怎么安装?

npm i -g typescript

5. 怎么创建支持TS的vue项目?

yarn create vite vite-ts-demo --template vue-ts

泛型

  1. 泛型是什么?

    答: 泛型的本质是参数化类型 ,就是把类型当做参数使用,代码里面就是<>

  2. 我们为什么需要用泛型?

    答:当我们在定义函数时,不知道其传入的参数是什么类型的,我们就可以用泛型去定义它,传入参数是什么类型泛型就是什么

  3. 使用泛型典型的应用场景?

    答:使用泛型典型的应用场景有 泛型类,泛型接口、泛型函数

    我就拿泛型接口举个例子:当我们需要创建一个参数为字符串的数组时,我们就可以使用泛型接口 Array<string> 这里Array就是泛型接口