TS基础
初学者技巧
书写技巧
-
先书写 js 部分,再添加 ts 类型注解
-
先写骨架,如:
type AxiosType = () => void
const obj = {}
-
后加:
type AxiosType= (config:{ url:string, method?:string}) => void
const obj = { name:string, age:number }
解读技巧
-
先删除掉函数的 ts 类型,解读功能,再去理解类型
TypeScript
介绍
TypeScript(简称:TS)是 JavaScript 的超集(JS 有的 TS 都有)。
TypeScript = Type + JavaScript(在 JS 基础之上,为 JS 添加了类型支持)。
TypeScript 是微软开发的开源编程语言,可以在任何运行 JavaScript 的地方运行。
TypeScript 相比 JS 的优势
-
更早(写代码的同时)发现错误,减少找 Bug、改 Bug 时间,提升开发效率。
-
程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验。
-
强大的类型系统提升了代码的可维护性,使得重构代码更加容易。
-
支持最新的 ECMAScript 语法,优先体验最新的语法,让你走在前端技术的最前沿。
-
TS 类型推断机制,不需要在代码中的每个地方都显示标注类型,让你在享受优势的同时,尽量降低了成本。
初体验
安装编译 TS 的工具包
npm i -g typescript
TS -> JS 的转化
tsc –V
检验是否成功
编译并运行 TS 代码
-
创建 hello.ts 文件(注意:TS 文件的后缀名为 .ts)。
-
将 TS 编译为 JS:在终端中输入命令,tsc hello.ts(此时,在同级目录中会出现一个同名的 JS 文件)。
-
执行 JS 代码:在终端中输入命令,node hello.js。
简化运行 TS 的步骤
使用 ts-node 包,直接在 Node.js 中执行 TS 代码。
npm i -g ts-node
使用:
ts-node hello.ts
常用类型
TS = JS +类型系统
可以显示标记出代码中的意外行为,从而降低了发生错误的可能性
类型注解
为变量添加类型约束
let age:number = 18
常用基础类型
TS 新增类型
联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any 等。
原始类型
number/string/boolean/null/undefined/symbol
let name:string = 'wendy'
数组类型
对象类型:object(包括,数组、对象、函数等对象)
数组类型的两种写法:(推荐使用 number[] 写法)
-
let numbers:number[] = [1,3,5]
-
let stings:Array<string> =['a','b']
数组中既有 number 类型,又有 string 类型
-
let arr:(number | string)[] = [1,'a']
类型别名
当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用。✨命名规范:首字母大写
type CustomArray = (number | string)
let arr1 = CustomArray = [1,'a']
type IPerson = {
name:string
age:number
sayHi():void
}
函数类型
函数参数和返回值的类型
1 单独指定参数、返回值的类型
-
const add = (num1:number):number=>{return num1}
2 同时指定参数、返回值的类型
-
const add:(num1:number)=>number=(num1)=>{return num1}
如果函数没有返回值,那么,函数返回值类型为:void。
-
const greet=(name:string):void=>{}
可选参数
const mySlice(start?:number,end?:number):void{}
注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数。
类型别名 type
作用:用于存储 TS 类型,方便
类型复用
语法:
type 类型别名 = 具体类型
命名建议:使用大驼峰命名法,类型别名首字母
大写
type MyType = (string | number)[]
let arr1:MyType
let arr2:MyType
type 实现继承
&
type Point2D { x:number;y:number}
type Point3D = Point2D & { z:number }
对象类型
TS 中对象的类型就是在描述对象的结构(有什么类型的属性和方法)
let person:{name:string;age:number;sayHi():void}={
name:'jack',
age:19,
sayHi(){}
}
-
多个属性类型时,使用
;
(分号)来分隔
-
方法的类型也可以使用箭头函数形式
{ sayHi: () => void }
。
可选属性
我们在使用 axios({ … }) 时,如果发送 GET 请求,method 属性就可以省略。
const myAxios(config:{url:string;method?:string}){}
可选参数默认值:
const greet = (name: string, b = 10): void => {
console.log(name, b)
greet('wendy')
接口
当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的。
注:
-
仅仅可以描述对象,在过去的框架中使用较多
-
使用 interface 关键字来声明接口。
-
声明接口后,直接使用接口名称作为变量的类型
-
因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)。
interface IPerson{
name:string
age:number
sayHi():viod
let person1:IPerson={
name:'jack',
age:19,
sayHi(){}
}
interface 实现继承
extends
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用。
interface Point2D { x:number;y:number}
interface Point3D extends Point2D { z:number }
interface(接口)和 type(类型别名)的对比:
相同点:
都可以给对象指定类型。
不同点:
接口 interface,只能为对象指定类型。
类型别名 type,不仅可以为对象指定类型,实际上可以为任意类型指定别名。
元组
let position:number[] = [39.213,28.21]
使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现
任意多个
数字。
更好的方式:元组
元组类型是另一种类型的数组,它确切地知道包含
多少个
元素,以及
特定
索引对应的类型
let position:[number,number]
类型推论
发生类型推论的 2 种常见场景:
1 声明变量并初始化时
2 决定函数返回值时
-
const add =(num1:number){ return num1 }
这两种情况下,类型注解可以省略不写!
✨工作经验:
能省略类型注解的地方就省略
(
偷懒
,充分利用TS类型推论的能力,提升开发效率)
✅技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型
// 类型推导
// 初始化
let age = 10
// b = 1 默认值
const add2 = (b = 1) => age + b
// 返回值推导
console.log(add2()) // 11
类型断言
使用类型断言来指定更具体的类型
getElementById 方法返回值的类型是 HTMLElement,该类型只包含所有标签公共的属性或方法,不包含 a 标签特有的 href 等属性。
const aLink = docment.getElementById('link') as HTMLAnchorElement
另一种语法,使用 <> 语法
const aLink = <HTMLAnchorElement> docment.getElementById('link')
❗️ 技巧:
在浏览器控制台,通过
console.dir(document.createElement("a"))
打印 DOM 元素,在属性列表的最后面,即可看到该元素的类型。
Image⇒ HTMLImageElement
a ⇒ HTMLAnchorElement
字面量类型
除字符串外,任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用
const str = 'Hello TS'
字面量类型配合联合类型一起使用,用来表示一组明确的
可选值列表
function changeDirection(direction:'up'|'down'|'left'|'right')
参数 direction 的值只能是 up/down/left/right 中的任意一个。
相比于 string 类型,使用字面量类型更加
精确、严谨
枚举
定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个。
开发经验
TS项目中可通过设计枚举结构,提高源代码的可读性
✅开发需求:
用户编辑界面,用户选择的性别 男 女 未知
后端接口参数要求传的是数字对应为 1 0 -1
enum Gender {
Boy = 1,
Girl = 0,
UnKnown = -1
const query = {
name: 'wendy',
gender: Gender.Girl
console.log('后端所需的参数', query)
// 数据反显
const res = {
code:200,
data:1
console.log('后端返回的参数回显', Gender[res.data]) // Boy
enum Direction { Up,Down,Left,Right}
function changeDirection(direction:Direction)
-
使用 enum 关键字定义枚举。
-
约定枚举名称、枚举中的值以大写字母开头。
-
枚举中的多个值之间通过 ,(逗号)分隔。
-
定义好枚举后,直接使用枚举名称作为类型注解。
changeDirection(Direction.Up)
数字枚举
默认为:从 0 开始自增的数值。
enum Gender1 {
// 默认自增
UnKnown = -1,
Boy, // 0
Girl, // 1
}
字符串枚举
字符串枚举的每个成员必须有初始值。✨建议还是使用
字面量类型 + 联合类型
替代 字符串枚举
any 类型
❗原则:不推荐使用 any!可以对该值进行任意操作,并且不会有代码提示。
隐式具有 any 类型的情况:1 声明变量不提供类型也不提供默认值 2 函数参数不加类型。
typeof
可以在类型上下文中引用变量或属性的类型(类型查询)
let p = {x:1,y:2}
function formatPoint(point:typeof p){}
typeof 出现在类型注解的位置
注意:typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)。
高级类型
class 类
实例属性初始化:
class Person{
age:number
gender = 'man' // 初始化数据 可省略类型注解
// 构造函数
constructor(age:number,gender:string){
this.age = age
this.gender = gender
// 实例方法:
scale(n:number):void{
this.x *= n
this.y *=n
}
构造函数:⬆
-
需要为构造函数指定类型注解,否则会被隐式推断为 any;构造函数不需要返回值类型。
实例方法:⬆
类继承
1 extends(继承父类)
2 implements(实现接口)
通过 implements 关键字让 class 实现接口
interface Singable{
sing():void
class Person implements Singable{
sing(){}
}
Person 类实现接口 Singable 意味着,Person 类中必须提供 Singable 接口中指定的所有方法和属性。
类成员可见性:
控制 class 的方法或属性对于 class 外的代码是否可见。
可见性修饰符:
1 public(公有的)(可省略)
2 protected(受保护的)
3 private(私有的)。
readonly:表示只读,用来防止在构造函数之外对属性进行赋值。
类型兼容性
两种类型系统:
1 Structural Type System(结构化类型系统)
类型检查关注的是值所具有的形状,如果两个对象具有相同的形状,则认为它们属于同一类型。
class类型兼容性
对于对象类型来说,y 的成员至少与 x 相同,则 x 兼容 y(成员多的可以赋值给少的)。
成员多的 Point3D 可以赋值给成员少的 Point
class Point{x:number;y:number}
class Point3D{x:number;y:number;z:number}
const p:Point = new Point3D()
接口兼容性
接口之间的兼容性,类似于 class。并且,class 和 interface 之间也可以兼容。
函数兼容性
需要考虑:1 参数个数 2 参数类型 3 返回值类型。
-
参数少的可以赋值给参数多的
-
相同位置的参数类型要相同(原始类型)或兼容(对象类型)。
-
如果返回值类型是原始类型,此时两个类型要相同。
-
如果返回值类型是对象类型,此时成员多的可以赋值给成员少的
2 Nominal Type System(标明类型系统)(比如,C#、Java 等)
交叉类型 &
功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
泛型
泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class 中。
一般不确定的地方就可以先丢泛型
理解为是一个函数,将类型当做参数传递
-
创建泛型函数:
const id = <T>(value: T): T => {
return value;
};
-
调用泛型函数:
const num = id<number>(10)
const str = id<string>('a')
-
简化调用泛型函数:✨建议使用
let num = id(10)
let str = id('a')
-
多泛型变量
const fn = <T, U>(a: T, b: U) => {};
-
泛型约束:
指定更加具体的类型
const id = <T>(value: T[]): T[] => {
console.log(val.length);
return value;
// ✨✅添加约束
interface ILength{
length:number
// T extends ILength 添加泛型约束
// 解释:表示传入的 类型 必须满足 ILength 接口的要求才行,也就是得有一个 number 类型的 length 属性
const fn = <T extends ILegnth>(val:T):T{
console.log(val.length);
return val
}
-
泛型接口:
// 定义泛型接口,接口就是描述对象的
interface Res<T> {
msg: string;
code: number;
result: T;
// 传递具体的类型
const res:Res<number[]> = {
msg: "成功",
code: 200,
result: [1, 2, 3]
res.result.push(4);
-
泛型类:
-
创建泛型类:
-
泛型工具类型:
泛型练习
/* 1. 需求: 封装一个函数,传入一个参数,包装成数组返回(可能是 number/string/boolean 等)
fn(20) => [20]
fn(true) => [true] */
const fn = <T>(val:T):T =>{
return [val]
/* 2. 需求: 封装一个函数,传入数组, 交换数组两项的位置(可能是 number/string/boolean 等)
fn([true,20]) => [20,true] */
const fn = <T,K>(arr:[T,K]):[K,T] =>{
return [arr[1],arr[0]]
/* 3. 一般后台返回的数据 res 中,result 是 null 或者数组,数组每一项是一个固定的结构对象,{id:1,name:"吃饭",done:false}封装符合的 Res 泛型接口 */
interface Res<T> {
msg:string,
code:number,
result:T
const res1:Res<null> = {
msg:'success',
code:200,
result:null
interface ResultType {
id:number,
name:string,
done:boolean
const res2:Res<ResultType[]> = {
msg:'success',
code:200,
result:[{
id:1,
name:'wendy',
done:false
}
keyof 关键词
关键字接收一个对象类型,生成其键名称 字符串的联合类型
interface Person {
name: string;
age: number;
gender: string;
const str: keyof Person = "gender";
// 两行代码是等价的
const myStr: "name" | "age" | "gender" = "gender";
ts 默认值为 unknown ----------对应js的 undefined
✨一般使用场景,用在 对象 的泛型封装中
const fn1 = <T = 1>(a: T) => {};
TS回顾
ts类型注解
需要添加导入、导出或空的
export {}
语句来使当前文件成为模块
:number
就是类型注解,为变量提供类型约束
原始类型
string number boolean undefined null
❗原始类型首字母是小写
✨项目一般不会主动声明undefined和null,一般在出现错误的时候回看到
数组类型
作用:约束数组每一项的类型
✨推荐语法:
类型[]
let arr:number[] = [1,2,3]
❌类型 string 的参数不能赋给类型 number 的参数
arr.push('1')
❌类型 number 的参数不能赋给类型 string 的参数
联合类型
语法:一根竖线
let arr:(string | number)[]
或者
let guess:string | number[]
❌不能将类型
boolean
分配给类型
(string | number)[]
❌❗不能将类型
string[]
分配给类型
string | number[]
函数类型
基本用法
定义
写法
const res = (num1:number,num2:number):number => {
return num1 + num2
}
type AddType = (num1:number,num2:number) => number
const add1:AddType = (num1,num2) =>{
return num1 + num2
}
函数类型-void类型
用于标注函数没有返回值
const sayHi = ():void =>{
console.log('hello')
}
❗ 返回值类型不为
void
的函数,必须有返回值
const sayHi2 = ():undefined =>{
console.log('hello')
return
}
函数类型-可选参数
语法:
参数后面
+
?
❗ 必选参数不能位于可选参数后
const fn = (n?:number):void => {
fn(10)
const mySlice(start?:number,end?:number):void => {
console.log(`开始索引为${start},结束索引为${end}`)
mySlice()
mySlice(1)
mySlice(1, 2)
对象类型
描述对象的结构,规定对象属性的方法和类型
基本用法
❌ 空对象
let obj1: {} = { name: 'zs' }
let obj2: object = { age: 18 }
❌存在问题:不指定对象属性和具体类型,无法访问对象的属性
✅ 给对象的属性指定类型
let obj3: { name:string } = { name:'zs' }
✅给对象的多个属性指定类型,一行书写用
;
隔开,如果换行可以不写
;
let obj4:{
name:string
age:number
}
拓展用法
方法可使用箭头函数
使用类型别名方便复用,提高代码复用性
属性和方法设置可选
🔔小练习:
1. 声明一个既有属性又有方法的对象类型
type ObjType = {
name:string
age:number
sayHi():void
sayNo:() => void
let obj5: ObjType = {
name:'zs',
age:19,
sayHi(){},
sayNo(){}
}
2. 为 axios({ url, method }) 添加 ts 类型注解
PS:如果是 get 请求 method 可以省略
const axiosType = {
url:string
method?:string
const axios = (config:axiosType) => {
console.log(config)
}
3. 创建一个学生对象,该对象中具有以下属性和方法:
-
属性:必选属性:姓名、性别、成绩,可选属性:身高
-
方法:学习、打游戏(可选)
const StuType = {
name:string
gender:string
score:number
height?:number
study():void
playGame?():void
const stu1:StuType = {
name: '悟空',
gender: '男',
score: 100,
height: 150,
study() {
console.log('打妖怪')
playGame() {
console.log('playGame~')
}
测试必选属性和方法:
stu1.name.trim()
stu1.score.toFixed(2)
stu1.study()
❗❗ 注意:可选属性和方法需要先判断再使用
if(stu1.height){
stu1.height.toFixed(2)
if (stu1.playGame) {
stu1.playGame()
}
对象语法补充-可选链操作符
经验:
可选属性或方法,可配合
?.
可选链操作符 安全使用。
安全调用写法1:判断
if(stu1.playGame){
stu1.playGame()
}
安全调用写法2:短路运算
stu1.playGame && stu1.playGame()
✅安全调用写法3:可选链操作符,最简洁
接口类型
用来定义对象的结构,type 和 interface 都可以定义对象的结构 ---- 按团队的习惯来即可
基本用法
interface IsStudent{
name:string
age?:number
sayHi():voids
const stu1:IsStudent = {
}
通过 type(类型别名)定义对象的结构
type TsStudent = {
name:string
age?:number
sayHi():void
}
扩展—接口继承
interface c{
x:number
y:number
// 先继承 Point2D 的所有属性和方法,在花括号内拓展添加自己的属性或方法
interface Point3D extends Piont2D{
z:number
}
扩展—类型别名和交叉类型
type Point3D = Point2D & { z:number }
type 和 interface
共同点:
都可以定义对象类型,在大多数情况下,可以根据个人(团队)喜好进行选择
区别:
type 不能出现重名
interface 重名会自动合并
TS自动类型推断
声明变量并提供初始值
函数的返回值
let name = 'tony'
❌
name = 123
根据初始值,自动推导的类型为
string
TS 字面量类型
基础语法:
TS 中允许把字面量直接当做类型使用
let name:'tony' = 'tony'
应用场景:
✨字面量类型可匹配联合类型使用,实现代码提示和类型校验
let method:'GET'|'POST'|'DELETE'|'PUT'
🔔练习实战:升级 axios 的 method 属性
type Config = {
url:string
// method?:string ❌范围太大了,没有代码提示,单词写错了不知道
method?:'GET'|'POST'|'DELETE'|'PUT'
// 为 axios 的参数添加类型注解
const axios = (config:Config) => {
console.log(config)
}
any类型
显式any:
逃避 TS 的类型检查,相当于让代码变回 JS,不检查类型
let obj:any
// ❗风险:以上的代码虽然 书写时 没有报错提示,但是代码 运行时 是可能出现错误的
obj = { name: 'zs', age: 18 }
obj.trim()
隐式any:
声明变量(函数参数),但没有设置类型或初始值
const add = (n1,n2)=>{ return n1+n2 }
✨经验:
-
第一原则:尽量避免使用 any 类型,any 使用得越多,程序漏洞越多
-
如果使用第三方库不知道是什么类型,可以先用 any 逃避 TS 的检查,让程序先跑起来
泛型(类型变量)
作用:提高代码的复用性
语法:先声明,再使用,T 表示类型变量
声明类型变量:
<T>
使用类型变量:
T
泛型别名
后端返回值类型,通过泛型提升可复用性
<T>
声明泛型
type ResponseType<T> = {
msg:string
code:number
data:T // T 使用泛型,使用前需声明
type User = {
name:string
age:number
}
应用
const userRes:ResponseType<User> = {
msg: 'ok',
code: 200,
data: {
name: 'zs',
age: 18,
}
项目中更安全的访问
userRes.data.name
泛型接口
interface IdFn<T>{
getId():T
getIds():T[]
const idFn:IdFn<number> = {
id:123,
getId(){
return 123
getIds(){
return [1,2]
}
泛型函数
function getId<T>(id:T){
return id
const res1 = getId<number>(1)
const res2 = getId<string>('wendy')
// ✨调用的时候,可以根据实参推导出参数的类型,可以省略不写
const res3 = getId(123)
泛型约束
extends
约束 T 的类型范围是 ‘GET’ | ‘POST’
function fn1<T extends 'GET' | 'POST'>(params:T)T{
return params
}
约束 T 的类型必须有 length 属性
function fn2<T extends { length:number }>(params:T):T{
console.log(param.length)
return param
}
封装抽离写法
interface Len{
length:number
function fn3<T extends Len }>(params:T):T{
console.log(param.length)
return param
fn2([1, 2, 3])
fn3('123')