对于TypeScript的自我修养(二)

发布于:2024-05-07 ⋅ 阅读:(17) ⋅ 点赞:(0)

书接上回

阅读过上一篇文章的小伙伴想必现在都已经安装了 TypeScript ,想必已经迫不及待的想用它来做一些什么了吧!,那么这篇文章,我们就简单的使用 ts 来做一些事情

基础使用

变量声明添加类型注解

这个地方并不难理解,我们平时使用js的变量都有类型,比如:string、number、boolean...,ts 就可以为我们在 js 中定义的变量添加独有的类型注解。添加的方式是: 标识符:数据类型 = 赋值
这样做有什么好处呢?更加明确你所使用变量的类型,在使用赋值时不会混乱,就好比一只猫,就不能把花草的类型赋值给猫。举个好玩点的🌰

 let cat: '猫'
 name = '喵喵'
 name = '汪汪' // 不能将狗的属性赋值给猫

例子开个玩笑方便理解,这样做我们命名的变量就会更加严谨,在项目中,假如后期有人维护想使用某个定义过的变量赋予一个不符合定义的类型,那么就会给维护人员一个提示。

js基础数据类型添加注解

js中的的基本数据类型:string,number,boolean,null,undefined,symbol

let name: string = '张三'
let age: number = 12
let isShow: boolean = true
let n: null = null
let u: undefined = undefined
name = 123 // Type 'number' is not assignable to type 'string'

像上面这样添加好类型注解后就不可随意更改定义变量的类型了,如果定义错误就会报错

变量的类型推断

在开发中,有时候为了方便起见我们井不会在声明每一个变量时都写上对应的数据类型,我们更希望可以通过TypeSeript本身的特性帮助我们推断出对应的变量类型

let name = 'code' // name:string推断
const age = 12 // age:12 字面量类型

那有人该说了,你这样写和 js 不就没有区别了嘛,那我还不如用 js 写代码哦?别着急,你这样试试

let name = 'code'
name = 123

试过之后发现,其实不写类型注解,我们也不能随意给赋值过的变量更改类型的。
那么上面的代码我们也能发现:

  • 使用let声明的变量推导出通用类型
  • 使用const声明的变量推导出字面量类型

为数组类型添加类型注解

为数组添加类型注解有两种方式,一种是 变量:类型[] , 另一种是泛型的写法,泛型这里先不进行介绍,写法是这样的变量:Array<类型>,那么在开发中,数组一般存放相同的数据类型。

let str: string[] = ['a', 'b']
let arr: Array<number> = [1, 2, 3]

为Object添加类型注解

为对象添加类型注解我们可以这样

let obj: { name: string, age: number } = {
  name: '1',
  age: 2
} // 也可以定义一个对象看一下类型推断出什么类型
let obj1: object = { name: 'zs', age: 12 } // 通用对象类型,这样写无法获取对象的属性,也不能设置

当然上面的写法肯定不够好,后面我们会使用类型别名来优化上面的代码

函数的类型(参数类型,返回值类型)

通过上面对基本数据类型添加注解的介绍,我们就可以对函数类型的参数以及返回值进行注解的添加了

function add(m1: string, m2: string):string {
  return m1 + m2
}

上面的代码含义为定义个add函数,两个参数都为string类型,返回值也为string类型

function add(m1, m2) {
  return m1 + m2
}
// 等同于
function add(m1:any, m2:any):any {
  return m1 + m2
}

如果不添加类型注解那么根据类型推断,变量和返回值都是any类型(后面会说到)。不添加类型注解是不安全的,就像给函数传一个数字和一个数组,是无法相加的。

函数类型的案例

type StuType = {
  name: string
  age: number
}

function addStu(name: string, age: number): StuType[] {
  const emptyArr: StuType[] = []
  emptyArr.push({ name, age })
  return emptyArr
}
addStu('zs', 12)

代码没有什么实质含义,就是为了练手使用,这里的 type 就是一个类型别名(相当于把注解提取出来了),函数返回的为 StuType 类型的数组,所以这个数组必须是带有 nameage 属性的对象数组,大家可以尝试返回一些不同的数据类型感受一下 ts 的魅力

匿名函数的参数

大家思考一下,匿名函数是否要添加类型注解呢?举个🌰给大家看一下

let arr = [1, 2, 3]
arr.forEach((item, index, array) => { 
})

一段简单的数组遍历的代码,如果我们想给匿名函数添加类型的话,想必是

let arr = [1, 2, 3]
arr.forEach((item:number, index:number, array:number[]) => { 
})

但其实,ts 已经悄悄通过上下文给我们做了类型推断了

image.png
所以对于大多数匿名函数都不需要议己去加类型注解(不是说不可以,自己可能会加错,不过相信各位大佬都很厉害,不会加错滴!)

对象类型

上面我们有介绍过给对象添加类型,我们可以通过起一个别名的方式 (type) 为对象添加类型注解,也可以定义接口 (interface) 为类型添加注解。

type方式添加类型注解

type TPeople = {
   name:string,
   age:number
}

let people:TPeople = {
   name: 'zs',
   age: 12
}

interface方式添加类型注解

interface IPeople {
  name: string
  age: number
}

let people: IPeople = {
  name: 'ls',
  age: 18
}

对象类型的可选属性

在声明的变量后添加 ?即可,代表这个属性不是必须的,定义对象时这个属性可传可不传。

interface IPeople {
  name: string
  age: number
  sex?: string
}

let people: IPeople = {
  name: '123',
  age: 12,
  // sex不赋值,不会出现报错
}

对象类型的只读属性

在声明的变量前添加readonly属性,代表这个属性是只读不可修改的。

interface IPeople {
  readonly name: string
  age: number
  sex?: string
}

let people: IPeople = {
  name: 'ls',
  age: 12,
}

people.name = 'ww' // 无法为name赋值,因为它是只读属性

any类型

当我们在开发中,一个变量的类型确实会发生改变,我们无法确定这个变量的具体类型时,我们就可以使用any注解,表示这个变量的类型是任意的.

let id:any // 不限制标识符的任意类型,可以对标识符进行任意操作
id = '1'
id = 1
id = {name:'ls'}
id.push()
let arr:any[] = [1,'2',{},[]]

我们在开发时要注意对any的使用,提倡大家不要把 TypeScript 变成 AnyScript,因为这样就和 JavaScript 没啥区别了,那使用这个工具也就没了意义。那什么场景适合使用any进行标注呢?当我们引用第三方库缺失了类型注解可以使用;或者在获取服务器数据时过于复杂,不想对其规定时也可使用。当然any使用不是死规定,大佬们也可在开发中随意使用,只要能把代码变得优雅那就是👌🏻。

unknown类型

unknown 类型和 any 类型类似,但是又有一些区别,unknown 类型用于描述不确定的变量,但在 unknown 类型的值上做任何事情都是不合法的。

let un: unknown

un = []

un.push() // un类型未知 不能进行push操作

想在unknown类型上进行操作,我们需要进行类型校验(类型缩小),缩小后根据对应类型进行对应的操作

let str: unknown

str = '123'

if (typeof str === 'string') {
  str.substring(1) // 校验后允许操作
}

所以和 any 的区别就在于:对 unknow 类型进行操作时是违法的

void类型

void类型通常是指一个函数没有返回值,那么这个函数就返回void类型

function add(n1: number, n2: number): number {
  return n1 + n2
}

function add1(n1: number, n2: number): void {
  console.log(n1, n2);
}

函数类型

我们如何表示一个变量是函数类型?这里也是需要使用 void

let fn:() => void

这个类型的用处就是当我们要对某一个变量进行调用执行时,来确定这个变量是函数类型才可以调用执行

function callBack(fn) {
  fn()
}

callBack(111) // 111可以传递但不是函数无法执行,但程序并不会提醒我们 因为fn相当于any类型

function callBack(fn: () => void) {
  fn()
}

callBack(111) // 不能将number类型赋值给()=>void类型

callBack(() => {
  console.log(111);
}) 

官方文档还有这样说

当基于上下文类型推导,推导出返回类型为void的时候,并不会强制函数一定不能返回内容

听着比较抽象,我们来看一个例子

let arr = [1, 2, 3]

arr.forEach((item, index) => {
  return item
})

上面代码中,arr数组遍历的方法返回的是void,但返回item不会报错,所以这种通过上下文推导出的void不会强制函数一定不能返回内容

never类型

never 类型在开发中很少去定义的,但是当我们开发 框架/工具,或是封装一些类型工具亦或者进行类型体操的练习时是会使用到,但像我目前这种小白,还达不到高阶的水平,所以我们还是先了解一下就可以,毕竟还是要先在项目中使用嘛。
never 表示永远不会发生值的类型,实际开发中可能在进行类型推导时推导出 never 类型

function fn() {
  return []
} // 通过推导 fn返回为never[]

举一个封装工具的例子吧

function parseMsg(msg: string | number) {
  switch (typeof msg) {
    case 'string':
      console.log(msg.length);
      break
    case 'number':
      console.log(msg.toString());
      break
    default:
      const check:never = msg
  }
}

上面的函数入参为联合类型,如果条件都不满足会走到default,但是这个函数因为定义了参数类型注解,所以不会走到default,所以check就是never类型。那么这个函数的意义就是当其他开发人员调用这个函数时,能够提醒开发人员在推展时要对拓展类型进行逻辑处理

function parseMsg(msg: string | number | boolean) {
  switch (typeof msg) {
    case 'string':
      console.log(msg.length);
      break
    case 'number':
      console.log(msg.toString());
      break
    default:
      const check: never = msg // 不能将boolean赋值给never,所以必须要对boolean进行处理
  }
}

// 处理
function parseMsg(msg: string | number | boolean) {
  switch (typeof msg) {
    case 'string':
      console.log(msg.length);
      break
    case 'number':
      console.log(msg.toString());
      break
    case 'number':
      console.log(msg);
      break
    default:
      const check: never = msg // 不能将boolean赋值给never,所以必须要对boolean进行处理
  }
}

元组类型(tuple)

元组类型在我理解看来其实就是有固定长度的数组,内部可以定义不同类型

let tup: [number, string] = [1, 'abc']

我们可以参照 ReactuseState 钩子函数 进一步深入理解一下元组类型

function useState(initVal: number): [number, (newVal: number) => void] {
  let stateVal = initVal
  function setVal(newVal: number) {
    stateVal = newVal
  }
  return [stateVal, setVal]
}

const [count, setCount] = useState(0)
setCount(3)
let c = count

根据我们现在所学习的知识来模仿一个 useState 函数(后面可以使用泛型改造这个函数),函数的返回值为一个具有 number 类型和一个 函数类型 的元组类型。通过元组类型的限制,我们可以清楚 count 是一个 number 类型,setCount 是一个 函数类型 可以调用并需要传参

总结

这篇文章主要针对 TypeScript 的一些基础常用类型的说明以及使用方法进行了介绍,万事开头难,当我们逐渐地理解了这些类型注解的用法之后,使用起来就得心应手了,可能说明的不够全面,或者您看到有错误的地方希望能够指正出来,还是那句话:“我希望能够通过我学习之后加上自己的理解能让抽象的东西变得通俗易懂起来,让大家更容易接受。”