likes
comments
collection
share

前端-TypeScript入门指南TypeScript是一种由微软开发的开源编程语言,‌它是JavaScript的一个超

作者站长头像
站长
· 阅读数 26

1.TypeScript是什么?

目标:能够说出什么是typescript

内容:

​ ● TS官方文档

​ ● TS中文参考-不再维护

前端-TypeScript入门指南TypeScript是一种由微软开发的开源编程语言,‌它是JavaScript的一个超

TypeScript 简称:TS,是JavaScript的超集, 简单来说就是:JS有的TS都有

前端-TypeScript入门指南TypeScript是一种由微软开发的开源编程语言,‌它是JavaScript的一个超

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

● TypeScript 是微软开发的开源编程语言,可以在任何运行JavaScript的地方运行

前端-TypeScript入门指南TypeScript是一种由微软开发的开源编程语言,‌它是JavaScript的一个超

JS:解释型,弱类型,动态语言

Java:编译型,强类型,静态语言

解释型:我们写的代码,无需进行编译,直接运行,也需要一个翻译器,一边翻译一边执行

编译型:人类写的是英语,机器不认识,需要一个编译器,将人类写的语言转换成机器识别的语言,这个过程叫编译

弱类型:声明变量时无需指定类型

强类型:声明变量时必须指定类型

动态语言:在代码执行的过程中可以动态添加对象的属性

静态语言:不允许在执行过程中随意添加属性

//JS
function Person(name){
    this.name=name
}

const p1=new Person('李华')
p1.gender='女'
console.log(p1.age.toFixed())  //报错

//Java语言
class Person{
    public String name;
    public Person(name){
        this.name=name
    }
}

Person p1=new Person('小米')
p1.gender='男'  //报错:Person没有gender属性
// p. //有完整的代码提示,如果不符合规则立马会报错

结论:JS的特点是灵活,高效,很短的代码就能实现复杂功能,缺点是没有代码提示,容易出错且编辑工具不会给任何提示,而java则相反

2.TypeScript 为什么要为 JS 添加类型支持?

  • 背景:JS 的类型系统存在“先天缺陷”,JS 代码中绝大部分错误都是类型错误(Uncaught TypeError)
  • 这些经常出现的错误,导致了在使用 JS 进行项目开发时,增加了找 Bug、改 Bug 的时间,严重影响开发效率

为什么会这样?

  • 从编程语言的动静来区分,TypeScript 属于静态类型的编程语言JavaScript 属于动态类型的编程语言
    • 静态类型:编译期做类型检查
    • 动态类型:执行期做类型检查
  • 代码编译和代码执行的顺序:1 编译 2 执行
  • 对于 JS 来说:需要等到代码真正去执行的时候才能发现错误(晚)
  • 对于 TS 来说:在代码编译的时候(代码执行前)就可以发现错误(早)

并且,配合 VSCode 等开发工具,TS 可以提前到在编写代码的同时就发现代码中的错误,减少找 Bug、改 Bug 时间

对比:

  • 使用 JS:

    1. 在 VSCode 里面写代码
    2. 在浏览器中运行代码 --> 运行时,才会发现错误【晚】
  • 使用 TS:

    1. 在 VSCode 里面写代码 --> 写代码的同时,就会发现错误【早】

    2. 在浏览器中运行代码

Vue 3 源码使用 TS 重写、Angular 默认支持 TS、React 与 TS 完美配合,TypeScript 已成为大中型前端 项目的首选编程语言

目前,前端最新的开发技术栈:

  1. React: TS + Hooks
  2. Vue: TS + Vue3
  • 注意: Vue2 对 TS 的支持不好~

3.安装编译TS的工具包

目标: 能够安装ts的工具包来编译ts

内容:

​ ● 问题:为什么要安装编译TS的工具包?

​ ● 回答:Node.js/浏览器,只认识JS代码,不认识TS代码。需要先将TS代码转化为JS代码,然后才能运行。

​ ● 安装命令:npm i -g typescript或者yarn global add typescript

​ 1.typescript包:用来编译TS代码的包,提供了tsc命令,实现了TS->JS的转化

​ 2.注意:MAC电脑安装全局包时,需要添加sudo获取权限 :sudo npm i -g typescript

​ yarn全局安装:sudo yarn global add typescript

验证是否安装成功:tsc -v(查看typescript的版本)

前端-TypeScript入门指南TypeScript是一种由微软开发的开源编程语言,‌它是JavaScript的一个超

4.编译并运行TS代码

目标: 能够理解typescript的运行步骤

内容:

​ 1.创建hello.ts文件(注意:TS文件的后缀名为.ts

​ 2.将TS编译为JS:在终端中输入命令,tsc hello.ts(此时,在同级目录中会出现一个同名的JS文件)

​ 3.执行JS代码:在终端中输入命令,node hello.js

1创建ts文件=》2编译TS=》3.执行JS

  • 说明:所有合法的JS代码都是TS代码,有JS基础只需要学习TS的类型即可
  • 注意:由TS编译生成的JS文件,代码中就没有类型信息了

真正在开发过程中,其实不需要自己手动的通过tsc把ts文件转成js文件,这些工作应该交给webpack或者vite来完成

5.创建基于TS的vue项目

目标: 能够使用vite创建vue-ts模板的项目

内容:

基于vite创建一个vue项目,使用typescript模板

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

  • yarn create vite

  • 输入项目名

  • 选择vue

  • 选择typescript

6.TypeScript常用类型

类型注解

目标: 能够理解什么是typescript的类型注解

内容:

  • TypeScript 是 JS 的超集,TS 提供了 JS 的所有功能,并且额外的增加了:类型系统
    • 所有的 JS 代码都是 TS 代码
    • JS 有类型(比如,number/string 等),但是 JS 不会检查变量的类型是否发生变化,而 TS 会检查
  • TypeScript 类型系统的主要优势:可以显示标记出代码中的意外行为,从而降低了发生错误的可能性
  1. 类型注解
  2. 常用基础类型

示例代码:

let age: number = 18
  • 说明:代码中的 : number 就是类型注解
  • 作用:为变量添加类型约束。比如,上述代码中,约定变量 age 的类型为 number 类型
  • 解释:约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错
  • 错误演示:
// 错误代码:
// 错误原因:将 string 类型的值赋值给了 number 类型的变量,类型不一致
let age: number = '18'

TypeScript常用基础类型

目标: 能够理解TypeScript中有哪些数据类型

内容:

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

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

原始类型

  • 原始类型:number/string/boolean/null/undefined/symbol

  • 特点:简单,这些类型,完全按照 JS 中类型的名称来书写

    let age: number = 18
    let myName: string = '老师'
    let isLoading: boolean = false
    
    // 等等...
    

数组类型

数组类型的两种写法

// 数组类型
// 声明数组的方式1(推荐用法)
let arr: number[] = [1, 2, 3, 4];

let arr2: string[] = ['sa', 'a', 'b'];
arr2.forEach(item => item.indexOf('a'));

//声明数组的方式2
let arr3: Array<number> = [1, 12, 23, 45];


联合类型

  • 解释:|(竖线)在 TS 中叫做联合类型,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种
  • 注意:这是 TS 中联合类型的语法,只有一根竖线,不要与 JS 中的或(|| 或)混淆了

{
    //需求:希望数组里面可以纯数字或字符串
    //联合类型:|
    let arr: (number | string)[] = [1, 2, 3, 'abc'];
    // 注意事项:| 的优先级较低,需要用()包裹提升优先级
    // 一旦使用联合类型,说明arr中存储的即可能是number也可能是string,所以会丢失一部分提示信息(只能提示共有的方法和属性)
    let timeId: number | null = null
    timeId = setTimeout(() => { },1000)
}

类型别名

  • 类型别名(自定义类型):为任意类型起别名
  • 使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用
  • 解释:
    1. 使用 type 关键字来创建自定义类型
    2. 类型别名(比如,此处的 CustomArray)可以是任意合法的变量名称
    3. 推荐使用大写字母开头
    4. 创建类型别名后,直接使用该类型别名作为变量的类型注解即可
// 需求:希望N个数组里面可以存数字或字符串
// 类型别名
type ArrType = (number | string)[]
let arr1: ArrType=[1,2,3,'abc']
let arr2: ArrType=[1,2,3,'abc']
let arr3: ArrType = [1, 2, 3, 'abc']
let arr4: ArrType = [1,'abc',3,'aaa']

// 灵活度很高,可以随意搭配组合使用
type ItemType = number | string
let arr5:ItemType[]=[1,2,3,'111']
let str1:ItemType='aaa'
str1 = 123

// 总结:将一组类型存储到变量里,用type来声明这个特殊的变量

// 玩花活儿
type s = string
type n = number

let str2: s = '123'
let num:n=1

函数类型

{
    // 需求:设置函数的参数类型 + 返回值类型
    // 具体:要求传入 number 类型的参数,加完后返回 number 类型的值
    // 程序员和函数之间的互动只有传入参数和返回值,所以为了让代码更少出错,需要对这两个地方进行类型约束
    // 在js中可以用以下方式定义函数,但ts中不行,因为ts要求我们必须给参数定义类型,而返回值它会自动推断
    // function add(a, b) {
    //     return a + b;
    // }
    
    // 函数声明
    // function 函数名(参数1:参数1类型,参数2:参数2类型):返回值类型{函数体}
    function add(a: number, b: number) {
        return a + b;
    }
    const result = add(1, 2);
    console.log(result);
    // add('1','2')  //报错
    // add(true,false);  //报错

    // 函数表达式
    const fn = function (a: number, b: number):number { 
        return a + b;
    }

    // 箭头函数
    // 注意事项:以前箭头函数如果只有一个参数,则可省略小括号,现在不行了
    // 结论:ts中箭头函数必须要小括号
    const sub = (a: number): number => {
        return a
    }
    

    // 函数的类型别名
    type FnType = (a: number, b: number) => number
    
    // 类型别名通常是给箭头函数/函数表达式使用,不会给函数声明使用
    // function math(a,b) {
    //      return a+b
    // }
    
    const fn2:FnType = function (a, b){ 
        return a*b
    }

    const sub2:FnType = (a, b) => { 
        return a-b
    }

    sub2(6,3)
}

void类型

{ 
    // 需求:定义一个打印文本的函数,不需要返回值
    // 如果不写return我们知道默认返回的是undefined,但是ts给我们类型推断为void
    // 在ts中写:undefined设置返回值类型的意思是:必须返回一个undefined
    // ts给我们提供了一个返回值类型:void意思就是没有返回值
    const sayHi = (content: string): void => { 
        console.log(content)
    }

    sayHi('你好世界')
}

函数可选参数

{ 
    // 需求:自己定义一个可选参数的函数
    let str:string='黄晓明真帅'
    console.log(str.slice());
    console.log(str.slice(1));
    // 包含头部,不包含尾部
    console.log(str.slice(1, 4));//从指定索引开始截取到指定索引
    
    // 在js中定义了形参,调用函数时可以不传实参,非常灵活
    // 在ts中定义了形参,调用时必须传入实参,否则报错
    // 如果要实现可选参数,加一个?
    // 注意事项:必选参数不能在可选参数后(符合常识)
    const print = (name?: string, gender?: string): void => {
        if (name && gender) { 
            console.log(name, gender);
            
        }
    }
    print('刘翠花','男')
    print('刘翠花')
    print()
}

对象类型

  • 注意:直接使用 {} 形式为对象添加类型,会降低代码的可读性(不好辨识类型和值)
  • 推荐:使用类型别名为对象添加类型
{ 
    // 对象类型
    //创建类型别名
    type person = {
        name: string,
        age: number,
        gender: string,
        hobby: string,
        //对象可选属性
        girlFriend?: string,
        //箭头函数形式的方法类型
        sayHi:(content:string) =>void
    }
    // ts就像在写注释,以前写的注释是给程序员看的,ts写的类型是给编辑器看的,程序员也可以看
    //使用类型别名作为对象的类型
    let obj1: person = {
        name: 'John',
        age: 25,
        gender: '男',
        hobby: '烫头',
        girlFriend: '乔碧萝',
        sayHi(content) {
            console.log('你好'+content);
            
         }
        
    }
    let obj2: person = {
        name: '某一帆',
        age: 55,
        gender: '未知',
        hobby: '抽烟',
        sayHi(content) { 
            console.log('真别学了:'+content);
            
        }
    }
    obj1.sayHi('清幽')
    obj2.sayHi('李华')

    // 传统
    if (obj1.girlFriend) { 
        obj1.girlFriend.concat()
    }

    // 优雅
    obj1.girlFriend && obj1.girlFriend.concat()

    // 最新:可选链操作符,前面有才会执行后面的函数
    obj1.girlFriend?.concat()
}

接口

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

  • 解释:

    1. 使用 interface 关键字来声明接口

    2. 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以 I 开头

    3. 声明接口后,直接使用接口名称作为变量的类型

    4. 因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)

interface Person{ 
    username: string
    age: number
    gender: string
    sayHi:() => void
}
const p1: Person = {
    username: 'John',
    age: 18,
    gender: '男',
    sayHi() { 
        console.log('我是榜一刘天一,I\'m rich');
        
    }
}
p1.sayHi();


// type ItemType = string | number

// type ArrayType = number[]

// type ArrayType2 = Array<number>

// 总结:interface和type的区别,interface只能约束对象,而type可以更灵活的使用
// 能用type就用type更灵活,更简单

interface vs type

  • interface(接口)和 type(类型别名)的对比:
  • 相同点:都可以给对象指定类型
  • 不同点:
    • 接口,只能为对象指定类型
    • 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名
  • 推荐:能使用 type 就是用 type

接口继承

如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用

interface Person{ 
    username: string
    age: number
    gender: string
    sayHi:() => void
}

// 接口继承:IStudent具备IPerson的所有约束规则
interface IStudent extends Person { 
    score: number
    sleep:()=>void
}

const p2: IStudent = {
    username: 'jack',
    age: 18,
    gender: '未知',
    sayHi() { 
        console.log('我是小红');
        
    },
    score:15,
    sleep() {
        console.log('笑笑正在睡觉');
        
    },
}
p2.sayHi()
p2.sleep()

// & 与连接符:既要满足前面的也要满足后面的
// | 或连接符:满足其中一个即可
// type如何和interface一样实现继承效果?
type s1 = {
    name: string
    age: number
    gender: string
}

type s2 = {
    score: number,
    sayHi:()=>void
} & s1

const NewStudent: s2 = {
    name: '李华',
    age: 25,
    gender: '男',
    score: 65,
    sayHi() {
        console.log('你好,小米');
    }
}

NewStudent.sayHi()

元组

  • 场景:在地图中,使用经纬度坐标来标记位置信息
  • 可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型
// 元组:限定数组的固定类型和数据长度
// 地图:经纬度
// 可以限制类型,但无法限制长度
// let arr:number[] = [112.125,36.123]
  • 使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现任意多个数字

  • 更好的方式:元组 Tuple

  • 元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型

   let arr:[number,number] =[112.125,36.123]

解释:

  1. 元组类型可以确切地标记出有多少个元素,以及每个元素的类型

  2. 该示例中,元素有两个元素,每个元素的类型都是 number

类型推论

  • 在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型
  • 换句话说:由于类型推论的存在,这些地方,类型注解可以省略不写
  • 发生类型推论的 2 种常见场景:
    1. 声明变量并初始化时
    2. 决定函数返回值时
{ 
    //类型推论
    // 主要在两种情况下发生:
    // 1.变量初始化
    // let a = 10 //简写
    // let a: number = 10 //写全
    
    // 2.函数返回值
    // function add(a:number,b:number) {  //简写
    //     return a*b
    // }

    function add(a:number,b:number):number {  //写全
        return a*b
    }

    let b: boolean = true
    // 小技巧:善用vscode摸一摸能力
    //如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型

}

字面量类型

  • 使用模式:字面量类型配合联合类型一起使用
  • 使用场景:用来表示一组明确的可选值列表
  • 比如,在贪吃蛇游戏中,游戏的方向的可选值只能是上、下、左、右中的任意一个
{ 
    let str1='Hello TS'
    // const声明的变量是不可修改的,意味着从始至终都必须是'Hello TS',所以TS将其当做一个类型来看,这种类型就被称为【字面量】类型
    const str2='Hello TS'

    // 字面量:10 20 'abc' [] {} /^$/
    // 字面量类型:刚刚出现的这些字面量都可以当做类型

    type Direction='上' | '下' | '左' | '右'
    function changeDirection(direction: Direction) { 
        console.log(direction);
        
    }
    changeDirection('下')

    type Gender='男' | '女' | '妖'
    function changeGender(gender: Gender) { 
        console.log(gender);
        
    }
    changeGender('妖')
    // 总结:字面量类型就是把字面量当做类型使用
}

枚举

  • 枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值
  • 枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
  • 解释:
    1. 使用 enum 关键字定义枚举
    2. 约定枚举名称以大写字母开头
    3. 枚举中的多个值之间通过 ,(逗号)分隔
    4. 定义好枚举后,直接使用枚举名称作为类型注解
{
    // 枚举:如果不设置值,默认从0开始
    // type Direction='上' | '下' | '左' | '右'
    enum Direction {
        Up ,
        Down ,
        Left,
        Right,
    }

    // 枚举内部做了一个事情,可以通过键找值,也可以通过值找键
    console.log(Direction);
    console.log(Direction[0]);

    
    function changeDirection(direction: Direction) {
        console.log(direction);
    }

    changeDirection(Direction.Up)
    changeDirection(Direction.Down)
    changeDirection(Direction.Left)
    changeDirection(Direction.Right)
    // 后端给前端的一些属性都是 0 1 2 3 这样的数字,例如:性别
    // 如果后端给的是100 上     101 下  102 左  103 右
}

枚举实现原理
  • 枚举是 TS 为数不多的非 JavaScript 类型级扩展(不仅仅是类型)的特性之一
  • 因为:其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)
  • 也就是说,其他的类型会在编译为 JS 代码时自动移除。但是,枚举类型会被编译为 JS 代码
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}

// 会被编译为以下 JS 代码:
var Direction;

(function (Direction) {
  Direction['Up'] = 'UP'
  Direction['Down'] = 'DOWN'
  Direction['Left'] = 'LEFT'
  Direction['Right'] = 'RIGHT'
})(Direction || Direction = {})

any类型

{
    // any类型:原则上不推荐使用,只有在迫不得已的时候才可以用一下,否则会变成AnyScript失去TS类型保护机制
    let a = 10;
    let str = 'abc';
    let arr: any = [];
    arr.push();

    // 以下情况都应该手动指定类型
    // function fn(a, b) {}
    // let b;
    // b = 10;
    // b = 'abc';
    // b = true;
}

  • 解释:以上操作都不会有任何类型错误提示,即使可能存在错误
  • 尽可能的避免使用 any 类型,除非临时使用 any 来“避免”书写很长、很复杂的类型
  • 其他隐式具有 any 类型的情况
    1. 声明变量不提供类型也不提供默认值
    2. 函数参数不加类型
  • 注意:因为不推荐使用 any,所以,这两种情况下都应该提供类型

类型断言

{ 
    //页面上有一个id为link的a标签
    // 我们知道它是a标签
    // 但是TS不知道
    // document.getElementById 的返回值是HTMLElement
    // 而HTMLElement身上没有href
    // 类型断言:强行指定获取到的结果类型
    const a = document.getElementById('link') as HTMLAnchorElement
    const box = document.getElementById('box') as HTMLDivElement
    const pp=document.getElementById('pp') as HTMLParagraphElement
    const img=document.getElementById('img') as HTMLImageElement

    const res = document.createElement('img')
    
    console.log(a.href);
    
    // if (a) { 
    //     console.log(a.href);
        
    // }
    // a && a.href
    // 总结:当函数获取到的结果类型较为宽泛时,我们又知道具体类型,就可以使用断言强行指定类型
}
  • 解释:
    1. 使用 as 关键字实现类型断言
    2. 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)
    3. 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了
  • 另一种语法,使用 <> 语法,这种语法形式不常用知道即可:
// 该语法,知道即可:
const aLink = <HTMLAnchorElement>document.getElementById('link')

技巧:在浏览器控制台,通过 __proto__ 获取 DOM 元素的类型

7.TypeScript泛型

泛型-基本介绍

泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class 中

{ 
    // 泛型
    // 需求:定义一个getId方法,传入一个值,返回这个值,可以传入任意类型
    // any可以解决问题,但也会带来问题:返回值也是any,没有提示了
    // function getId(val:any) {
    //     return val
    // }

    // console.log(getId(123));
    // console.log(getId('aaa'));

    // 解决方案:泛型
    // 期望:调用getId函数时来指定传入参数的类型
    // <T> -> 在声明泛型
    // val:T -> 使用泛型
    // 调用函数时传入泛型指定的具体类型
    function getId<T>(val: T) { 
        return val
    }

    console.log(getId<number>(123));
    console.log(getId<string>('abc'));
    console.log(getId<boolean>(true));
    
    const result=getId<number>(123)
    const result2=getId<string>('abc')
    const result3=getId<boolean>(false)
    
} 

简化泛型函数调用

  • 解释:
    1. 在调用泛型函数时,可以省略 <类型> 来简化泛型函数的调用
    2. 此时,TS 内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量 Type 的类型
    3. 比如,传入实参 10,TS 会自动推断出变量 num 的类型 number,并作为 Type 的类型
  • 推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读
  • 说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数
// 简化写法:调用时可以不加<类型> TS会自动推断类型
    // const result:123 得到的结果是一个字面量类型,其实完全可以用
    const result =getId(123)
    const result2=getId('abc')
    const result3=getId(false)

泛型约束

  • 默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性
  • 比如,getId('abc') 调用函数时获取参数的长度:
//泛型 - 类型约束

    // 简化写法:调用时可以不加 <类型> TS会自动推断类型
    // const result:123 得到的结果是一个字面量类型,其实完全可以用

    // function getId<T>(val: T) {
    //     console.log(val.length); //报错:T是一个未知的类型,所以无法得知它到底有没有length

    //     return val
    // }

    // const result1=getId('abc')
    // const result2=getId(123)
    // const result3=getId(false)
  • 解释:Type 可以代表任意类型,无法保证一定存在 length 属性,比如 number 类型就没有 length
  • 此时,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)
  • 添加泛型约束收缩类型,主要有以下两种方式:1 指定更加具体的类型 2 添加约束

指定更加具体的类型

指定更加明确的类型:T类型的数组,val确定就是一个数组了,所以可以用length

function getIds<T>(val: T[]) {
        console.log(val.length); //val:已经明确是一个数组了,所以自然会有数组的所有属性
        return val;

    }
    const result=getIds([1,2,3])
function getId<T>(val: T) {
        // 类型收缩:比较麻烦
        if (typeof val === 'string') {
            console.log(val.length);
        } else if (typeof val === 'number') {
            val.toFixed();
        }
        return val;
    }

添加约束

// 定义接口
    interface ILength { 
        length:number
    }
    // 添加约束
    function getIds<T extends ILength>(val: T){ 
        console.log(val.length);
        return val
        
    }

    console.log(getIds<number[]>([1, 2, 3]));
    console.log(getIds<string>('abcd'));
    console.log(getIds<number>(123)); //报错
    console.log(getIds<boolean>(true)); //报错
  • 解释:
    1. 创建描述约束的接口 ILength,该接口要求提供 length 属性
    2. 通过 extends 关键字使用该接口,为泛型(类型变量)添加约束
    3. 该约束表示:传入的类型必须具有 length 属性
  • 注意:传入的实参(比如,数组)只要有 length 属性即可(类型兼容性)

多个类型变量

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)

{ 
    // 多个泛型
    // 需求:定义一个函数,传入一个对象,再传入一个字符串属性名,返回属性值
    // 新语法:keyof O 意思就是 O 的所有属性
    function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
        return obj[key]
    }
    
    const p1 = {
        name: '刘狄威',
        gender:'男'
    }

    //const p2 = {
    //    score: 59,
    //    hobby:'抽烟喝酒烫头'
    //}
    const result1 = getProp(p1, 'name')
    const result2 = getProp(p1, 'age')
    const result3 = getProp(p1, 'gender')
    console.log(result1);
    //getProp(p2, 'hobby')
    
    // keyof 常规用法
    // type Friend = {
    //     name: string
    //     age: number
    //     hobby:string
    // }
    // keyof Friend

}

解释:

  1. 添加了第二个类型变量 Key,两个类型变量之间使用 , 逗号分隔。
  2. keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
  3. 本示例中 keyof Type 实际上获取的是 p1对象所有键的联合类型,也就是:'name' | 'gender'
  4. 类型变量 Key 受 Type 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性
// Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错
// 如果要用到 对象 类型,应该用 object ,而不是 Object
function getProperty<Type extends object, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key]
}

泛型接口

泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性

{ 
    // 泛型接口
    interface Student<T> { 
        id: number
        name: T
        hobby:T[]
    }

    let s1: Student<string> = {
        id: 1,
        name: '小帅',
        hobby:['抽烟','喝酒','烫头']
    }

    const s2: Student<number> = {
        id: 123,
        name: 0,
        hobby:[1,2,3,4]
    }

    const arr1: number[] = [1, 2, 3, 4]
    const arr2: Array<number> = [1, 2, 3, 4]
    const arr3: Array<string> = ['喝酒', '烫头', '蹦迪'] 
    arr2.push(123)
    arr3.push('1')
}

解释:

  1. 在接口名称的后面添加 <类型变量>,那么,这个接口就变成了泛型接口。
  2. 接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量
  3. 使用泛型接口时,需要显式指定具体的类型(比如,此处的 Student)。

JS 中的泛型接口

实际上,JS 中的数组在 TS 中就是一个泛型接口。

const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach

const nums = [1, 3, 5]
// 鼠标放在 forEach 上查看类型
nums.forEach
  • 解释:当我们在使用数组时,TS 会根据数组的不同类型,来自动将类型变量设置为相应的类型
  • 技巧:可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看具体的类型信息

泛型工具类型

  • 泛型工具类型:TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作
  • 说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。 这些工具类型有很多,主要学习以下几个:
  1. Partial<Type>
  2. Readonly<Type>
  3. Pick<Type, Keys>
Partial
  • Partial 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。
type Props =  {
  id: string
  children: number[]
}

type PartialProps = Partial<Props>
  • 解释:构造出来的新类型 PartialProps 结构和 Props 相同,但所有属性都变为可选的。
Readonly
  • Readonly 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)。
type Props =  {
  id: string
  children: number[]
}

type ReadonlyProps = Readonly<Props>
  • 解释:构造出来的新类型 ReadonlyProps 结构和 Props 相同,但所有属性都变为只读的。
let props: ReadonlyProps = { id: '1', children: [] }
// 错误演示
props.id = '2'
  • 当我们想重新给 id 属性赋值时,就会报错:无法分配到 "id" ,因为它是只读属性。
Pick
  • Pick<Type, Keys> 从 Type 中选择一组属性来构造新类型。
interface Props {
  id: string
  title: string
  children: number[]
}
type PickProps = Pick<Props, 'id' | 'title'>
  • 解释:
    1. Pick 工具类型有两个类型变量:1 表示选择谁的属性 2 表示选择哪几个属性。 2. 其中第二个类型变量,如果只选择一个则只传入该属性名即可。
    2. 第二个类型变量传入的属性只能是第一个类型变量中存在的属性。
    3. 构造出来的新类型 PickProps,只有 id 和 title 两个属性类型。

8.TypeScript与Vue

参考链接:vuejs.org/guide/types…

vue3配合ts中,还需要额外安装一个vscode插件:TypeScript Vue Plugin

前端-TypeScript入门指南TypeScript是一种由微软开发的开源编程语言,‌它是JavaScript的一个超

main.ts文件

import { createApp } from "vue";
import App from './App.vue'
createApp(App).mount('#app')

defineProps与Typescript

1.defineProps配合vue默认语法进行类型校验(运行时声明)

// 运行时声明
defineProps({
    money:{
        type:Number,
        required:true
    },
    car:{
        type:String,
        required:true
    }
})

2.defineProps配合ts的泛型定义props类型校验,这样更直接

//使用ts的泛型指令props类型
defineProps<{
    money:number
    car?:string
}>()

3.props可以通过解构来指定默认值

<script lang="ts" setup>
//使用ts的泛型指令props类型
const {money,car='小黄车'}=defineProps<{
    money:number
    car?:string
}>()
</script>

如果提供的默认值需要在模板中渲染,需要额外添加配置

vuejs.org/guide/extra…

// vite.config.js
export default {
	plugins: [
	vue({
	reactivityTransform: true
	})
	]
}

defineEmits与Typescript

1.defineEmits配合运行时声明

// 非TS语法
const emit = defineEmits(['getGift'])

2.defineEmits配合ts类型声明,可以实现更细粒度的校验

// TS语法:既要对事件名做约束也要对参数做约束
const emit = defineEmits<{
	// 泛型对象中有几个事件就写几个约束
	// ():中有 n 个参数,固定的是 e ,其他就根据具体参数来决定
	// e 表示事件名
	// e: 事件名
	// 后面语法为 参数名:类型
	// 完整语法   (e:事件名,参数名1:类型,参数名2:类型):void
    (e:'getGift',gift:string,msg:string):void
}>()

const hClick=()=>{
    // 子传父给父组件传递参数
    emit('getGift','孙子','你好我来了')
}

ref与Typescript

1.通过泛型指定value的值类型,如果是简单值,该类型可以省略

// 类型推论推导出来为string
const msg1 = ref('你好世界')

// 通过泛型设置为string
const msg2 = ref<string>('世界你好')

2.如果是复杂类型,推荐指定泛型

// 设置一个引用数据类型的数据
type Todo = {
    id: number,
    name: string,
    done: boolean
}
const list = ref <Todo[]>([
    { id: 1, name: '吃饭', done: false },
    { id: 2, name: '睡觉', done: true },
    { id: 3, name: '打呼噜', done: false },
    
])
let obj: Todo = list.value[0]

// 设置一个引用数据类型的数据
type TodoList = {
    id: number
    name: string
    done:boolean
}[]
const list1 = ref<TodoList>([
    { id: 1, name: '吃饭', done: false },
    { id: 2, name: '睡觉', done: true },
    { id: 3, name: '打呼噜', done: false },
])

// 开发中更建议使用ref来定义变量
// 如果使用reactive更新数据时需要注意丢失响应式的问题

computed与Typescript

  1. 通过泛型可以指定computed计算属性的类型,通常可以省略
const leftCount = computed<number>(() => {
	return list.value.filter((item) => item.done).length
})
console.log(leftCount.value)

事件处理与Typescript

const move = (e: MouseEvent) => {
mouse.value.x = e.pageX
mouse.value.y = e.pageY
}
<h1 @mousemove="move($event)">根组件</h1>

Template Ref与Typescript

<template>
<img ref="automan" 	src="https://t14.baidu.com/it/u=3985002590,2229181523&fm=224&app=112&size=w931&n=0&f=JPEG&fmt=auto?sec=1720544400&t=863a068ee62912350abe435dca9df5aa" alt="">
<button @click="getAutoman">点我获取奥特曼</button>

</template>

<script setup lang="ts">
// DOM中的ref
const img=document.createElement('img')
const automan = ref<HTMLImageElement | null>(null)

const getAutoman = () => { 
    // 获取图片没问题
    // console.log(automan.value);
    // 新增需求:点击后修改图片地址
    if (automan.value) {
        automan.value.src=''
     }
    
}
</script>
<style scoped>

</style>

可选链操作符

目标: 掌握js中的提供的可选链操作符语法

内容:

可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是

否有效。

参考文档:developer.mozilla.org/zh-CN/docs/…

	// 新增需求:获取图片地址
    // if (automan.value) {
    //     console.log(automan.value.src);

    //  }
    // 优雅
    // console.log(automan.value && automan.value.src);

    // 更优雅
    // ?. 可选链操作符
    // ?. 可以让我们不需要确定每个属性是否真的有值,如果中间某一个环节没有值,整个表达式返回undefined
    console.log(automan.value?.src);

非空断言

内容:

  • 如果我们明确的知道对象的属性一定不会为空,那么可以使用非空断言 !

    // 非空断言(慎用),你百分百确定有值的时候才用
    console.log(automan.value!.src); //如果没有也强行用
    
  • 注意:非空断言一定要确保有该属性才能使用,不然使用非空断言会导致bug

9.TypeScript 类型声明文件

基本介绍

今天几乎所有的 JavaScript 应用都会引入许多第三方库来完成任务需求。

这些第三方库不管是否是用 TS 编写的,最终都要编译成 JS 代码,才能发布给开发者使用。

我们知道是 TS 提供了类型,才有了代码提示和类型保护等机制。

但在项目开发中使用第三方库时,你会发现它们几乎都有相应的 TS 类型,这些类型是怎么来的呢? 类型声明文件

  • 类型声明文件:用来为已存在的 JS 库提供类型信息

  • TS 中有两种文件类型:1 .ts 文件 2 .d.ts 文件

  • .ts 文件:

    1. 既包含类型信息又可执行代码
    2. 可以被编译为 .js 文件,然后,执行代码
    3. 用途:编写程序代码的地方
  • .d.ts 文件:

    1. 只包含类型信息 的类型声明文件

    2. 不会生成 .js文件,仅用于提供类型信息,在.d.ts文件中不允许出现可执行的代码,只用于提供类型

    3. 用途:为 JS 提供类型信息

  • 总结:.ts 是 implementation (代码实现文件);.d.ts 是 declaration(类型声明文件)

  • 如果要为 JS 库提供类型信息,要使用 .d.ts 文件

// 由于第三方包最终都会打包成js文件,就会丧失ts的类型特性
// 所以这些第三方库都会提供一个 .d.ts 结尾的类型声明文件,来告诉开发者这个库所拥有的所有类型

内置类型声明文件

  • TS 为 JS 运行时可用的所有标准化内置 API 都提供了声明文件
  • 比如,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息:
const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach
  • 实际上这都是 TS 提供的内置类型声明文件
  • 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容
  • 比如,查看 forEach 方法的类型声明,在 VSCode 中会自动跳转到 lib.es5.d.ts 类型声明文件中
  • 当然,像 window、document 等 BOM、DOM API 也都有相应的类型声明(lib.dom.d.ts)

第三方库的类型声明文件

  • 目前,几乎所有常用的第三方库都有相应的类型声明文件
  • 第三方库的类型声明文件有两种存在形式:1 库自带类型声明文件 2 由 DefinitelyTyped 提供。
  1. 库自带类型声明文件:比如,axios
  • 查看 node_modules/axios 目录

解释:这种情况下,正常导入该库,TS 就会自动加载库自己的类型声明文件,以提供该库的类型声明。

  1. 由 DefinitelyTyped 提供
  • DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
  • DefinitelyTyped 链接
  • 可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:@types/*
  • 比如,@types/react、@types/lodash 等
  • 说明:在实际项目开发时,如果你使用的第三方库没有自带的声明文件,VSCode 会给出明确的提示
import _ from 'lodash'

// 在 VSCode 中,查看 'lodash' 前面的提示
  • 解释:当安装 @types/* 类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明
  • 补充:TS 官方文档提供了一个页面,可以来查询 @types/* 库
  • [@types/* 库](

自定义类型声明文件-共享数据

  1. 项目内共享类型
  2. 为已有 JS 文件提供类型声明

项目内共享类型

  • 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。

  • 操作步骤:

    1. 创建 index.d.ts 类型声明文件。
    2. 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
    3. 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。

    案例相关代码:

    App.vue

    <template>
        <div>
            我是App组件
        </div>
        <Son v-for="item in list" :key="item.id" :Todo="item"></Son>
    </template>
    
    <script setup lang="ts">
    import { ref } from 'vue';
    import Son from './components/Son.vue'
    import {TodoItem } from './types/todo'
    // type TodoItem = {
    //     id: number
    //     name: string
    //     done: boolean
    // }
    const list = ref<TodoItem[]>([
        { id: 1, name: '吃饭', done: false },
        { id: 2, name: '睡觉', done: true },
        { id: 3, name: '打呼噜', done: false },
    ])
    </script>
    
    <style scoped>
    
    </style>
    
    

    Son.vue

    <template>
        <div>我是子组件</div>
        
        <input type="checkbox" :checked="Todo.done">
        <label>{{ Todo.name }}</label>
    </template>
    
    <script setup lang="ts">
    import {TodoItem } from '../types/todo';
    // type TodoItem = {
    //     id: number
    //     name: string
    //     done: boolean
    // }
    // 用TS语法接收父组件传递过来的数据
    defineProps <{Todo:TodoItem}>()
    </script>
    
    <style scoped></style>
    
    

    todo.d.ts

    export type TodoItem = {
        id: number
        name: string
        done: boolean
    }
    

为已有 JS 文件提供类型声明

  1. 在将 JS 项目迁移到 TS 项目时,为了让已有的 .js 文件有类型声明。
  2. 成为库作者,创建库给其他人使用。
  • 注意:类型声明文件的编写与模块化方式相关,不同的模块化方式有不同的写法。但由于历史原因,JS 模块化的发展 经历过多种变化(AMD、CommonJS、UMD、ESModule 等),而 TS 支持各种模块化形式的类型声明。这就导致 ,类型声明文件相关内容又多又杂。
  • 演示:基于最新的 ESModule(import/export)来为已有 .js 文件,创建类型声明文件。 开发环境准备:使用 webpack 搭建,通过 ts-loader 处理 .ts 文件。

类型声明文件的使用说明

  • 说明:TS 项目中也可以使用 .js 文件。
  • 说明:在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。
  • declare 关键字:用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
    1. 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
    2. 对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。
let count = 10
let songName = '痴心绝对'
let position = {
	x: 0,
	y: 0
}
function add(x, y) {
	return x + y
}
function changeDirection(direction) {
	console.log(direction)
}
const fomartPoint = point => {
	console.log('当前坐标:', point)
}
export { count, songName, position, add, changeDirection, fomartPoint }

定义类型声明文件

declare let count:number
declare let songName: string
interface Position {
	x: number,
	y: number
}
declare let position: Position
declare function add (x :number, y: number) : number
type Direction = 'left' | 'right' | 'top' | 'bottom'
declare function changeDirection (direction: Direction): void
type FomartPoint = (point: Position) => void
declare const fomartPoint: FomartPoint
export {
	count, songName, position, add, changeDirection, fomartPoint
}
转载自:https://juejin.cn/post/7390534947104227368
评论
请登录