本文正在参加「金石计划 . 瓜分6万现金大奖」
引言
随着前端不断的发展前进,ts逐渐成为时代的宠儿,许多公司都逐步将ts引入项目开发中。作为一名合格的前端学习者,我们必须紧跟发展,扎实学好ts。
安装
1. 安装typescript
npm i -g typescript
2. 安装本地运行ts环境
npm i -g ts-node
3.创建文件夹并打开终端
tsc --init
4.建立ts文件1.ts,运行
ts-node 1.ts
一:基础类型
ts的基础类型约束包含许多,除了js中常见的基础类型外,还引入了any,unknown,never等类型
- number
- string
- boolean
- sybmol
- any
- unknown
- never
1.类型约束后,无法赋给其他类型数据
let str:string = "dzp"
str = "xxx"
str = 122 //报错
2.any可以赋值给任何数据类型,同时任何数据类型也可以赋值给any类型
let node:any = 1
let str:string ="xx"
node = str
3.unkown只能赋值给any类型,同时任何数据类型可以赋值给unkown类型
let node:unknown = 1
node = "2"//ok
node = true //ok
let num:number = node //error,unknown类型数据只能赋给unknown和any类型的数据
4.never
never表示永远不可能,通常用于死循环函数或者异常类型定义上
function fn():never {
while(true) {
console.log(1)
}
}
二:数组
ts中数组的定义有两种方式
方式一:type[]
let arr:number[] = []
arr.push(1)//ok
arr.push("1")//error
方式二:Array<type>
let arr:Array<number> = []
arr.push(1)//ok
arr.push("1")/error
注意点
数组的两类定义方式都可以同时存在多种类型
let arr1:(number|string)type = [1,"1"]
arr.push(true)//false,只能存在number和string类型
let arr2:Array<number|string> = [1,"1"]
arr2.push(true)//false 同理
三:元祖
格式
[type1,type2,type3...]
ts中引入的元组类型是一类特殊的数组,它不仅要求类型相同,并且元素数量也必须相同。
let arr:[number,string,boolean] = [1,"da",true]
arr[3] = 1//error
四:对象
对象类型的约束就是由其他普通类型约束组成
let objType:{
name:string,
age?:number//可添加age,也可以不添age属性
} = {
name:"dzp"
}
console.log(objType)
五:函数
函数类型约束
指定了函数的参数类型和返回值类型
function fn1(name:string,age:number):string{
}
fn1("dzp",22)
fn1("dzp")//error,必须传递指定参数
函数可选参数
函数的可选参数只能出现参数的最后面,如下指定了age参数可以添加也可不添加
function fn(name:string,age?:number):string {
if(age) return name
return name+age
}
fn("dzp",22)
fn("xx")
函数默认参数
函数的默认参数添加后,可以选择传递,也可以选择不传递
function fn(name:string,age:number=10){
}
fn("dzp")
fn("dzp",22)
函数重载
函数重载:函数的名称相同,但是函数的参数类型或者函数的参数个数不同称为函数重载。
function fn(name:string,age:number):void;
function fn(name:number,age:string):void;
function fn(name:string|number,age:string|number) {
if(typeof name === 'string') {
console.log("执行第一个")
} else {
console.log("执行第二个")
}
}
fn("dzp",22)//执行第一个
fn(22,"dzp")//执行第二个
六:枚举
ts中的枚举包含了三类
- 字符串枚举
- 数字枚举
字符串枚举
字符串枚举必须每个都添加默认值
enum str {
a='a1',
b='b1',
c='c1'
}
let num:str = str.a
console.log(num)//‘a1’
数字枚举
数字枚举可以不添加默认值,不添加默认值默认从下标0开始赋值。数字枚举同时支持下标访问和对象访问两种方式。
enum e {
a,
b,
c = 10,
d
}
let num:e = e.b
console.log(num)//1
七:类型断言
类型断言
类型断言告诉编译器我自己确定了此变量的类型,编译器不要报错。小伙伴们可以按照类型强制转换去理解。
使用方式 变量 as type
下面这种场景会报错,因为ts无法确定你是否是字符串还是数字类型
function fn (name:string|number) {
let len = name.length
}
使用类型断言明确告诉编译器我是字符串
function fn (name:string|number) {
let len = (name as string).length
}
非空断言
非空断言指告诉编译器我的类型是非空的,排除空类型的情况
使用方式 变量!
function fn(name:string | null | undefined) {
console.log(name.length)//error,编译器不确定是否是unll,undefined
console.log(name!.length)//ok,明确告诉编译器是一定不是空
}
确定赋值断言
确定赋值断言指告诉编译器我的变量是赋值的
使用方式 变量!
let name:string
console.log(name);//error,没有赋值
console.log(name!);//ok欺骗编译器,我赋值了,你不要检测。
八:类型守卫
类型守卫就是使用 typeof、in、instanceof等类型判断约束类型的范围。
九:类型别名 type
类型别名:给已有的类型换个名称
type str = string
let s:str = "age"
注意点
1. type不可以重复定义
type str = string
type str = number;//error
2. 可以定义普通数据类型
type str = string
let s:str = "abc"
3. 可以定义对象类型
type objs = {name:string,age:number}
let obj:objs = {name:"aaa",age:2}
十:接口 interface
接口和类型别名十分相似,都是对类型做进一步的约束,特殊的是接口只能作用在对象、函数上。
1. 特殊约束
?:表示该属性对象可以添加,也可以不添加
readonly key:表示该属性只能读,无法修改和删除
[fn:string]:any 表示对象可以添加任意额外属性
interface inter {
name:string,
age:number,
sex?:number,
readonly height:number,
info(name:string):number,//定义对象内部的方法
[fn:string]:any//可选属性
}
let obj:inter = {
name:"dzp",
age:22,
height:100,
info(name){
return 1
},
other1:'oo',
other2:100
}
2.继承
interface是可以通过extends完成属性的继承。
interface father{
name:string
}
interface son extends father {
age:number
}
let o:son = {
name:"dzp",age:22
}
3.重复定义
与type不同的是,type定义相同的类型变量会报错,而interface定义相同的类型变量是允许的,并且还会自动合并多次定义的属性
interface inter{
name:string
}
interface inter{
age:number
}
let o:inter = {
name:"dzp",
age:22
}
4.interface和type的索引获取类型
interface和type都可以通过索引构造新的类型
-
type
type t1={ name:string age:number } type tt1 = t1["name"|"age"]//string|number -
interface
interface t1{ name:string age:number } type tt1 = t1["name"|"age"]//string|number
5.type与interface区别
- type不可以重复定义,interface可以重复定义
- type可以约束普通数据类型也可以作对象类型,而interface只能用于对象数据类型约束
- type的拓展通过&,interface的拓展通过继承实现。
十一:class
-
class引入了成员属性,成员方法,静态属性,静态方法等
-
属性前加#表示私有属性
-
属性前加readonly表示只读属性,不可以修改。
-
引入了public,private,protected修饰符
class People { name!:string//成员属性 #age!:number//私有成员属性 static sex:number = 1//静态属性 constructor(name:string,age:number) { this.name = name this.#age = age } static getName():string {//静态方法 return this.name } getAge():number{//成员方法 return this.#age } }
十二:范型
范型是ts中的重点与难点,学会范型其它知识点也就十分容易了。此处的范性和java,c++的范性约束是一样的,学过的同学肯定感到十分熟悉。
1. 范性在函数的应用
//普通函数的定义
function fn<T,V>(name:T,age:V):T {
console.log(name)
return name;
}
//箭头函数中定义
const fn = <T,V>(name:T,age:V):T => {
return name;
}
fn<string,number>("dzp",22)
2. 范性在接口的应用
interface inter<T> {
name:T
}
let o:inter<string> = {name:"dzp"}
3. 范性在类型别名的应用
type t<T> = {
name:T
}
let o:t<string> = {name:"dzp"}
4. 范性在类的应用
class clacArray<T,U>{
name!:T
age!:U
}
十三: 常见关键词搭配
extends
检验是否存在某个属性K
type type1 = {
length:number
}
function fn<T extends type1> (name:T) {
return name.length
}
fn<number[]>([1,2,3])//OK
fn<string>("dzp")//ok
fn<number>(1)//error
typeof
根据变量生成其类型约束
let obj = {name:"dzp",age:22}
type t =typeof obj
//此时的t相当于 t={name:string,age:number}
keyof
生成接口类型的所有k(此次是key,而不是生成所有类型)
type t = {name:string,age:number}
type t2 = keyof t
//此次相当于 type t2 = 'name' | 'age'
[]索引访问
[]可以获取type或者接口interface类型定义中某个属性的具体类型
type o = {name:string,age:number}
type t = o['name']
//此处相当于type t = string
in
映射遍历枚举类型
type t8 = name|age
type t9 = {
[p in t8]:any
}
//此处相当于 type t9 = {name:any,age:any}
Partial
将接口或type定义的对象类型全部变成可选的?
type t = {name:string}
let o:t<Partial> = {}//name属性变成可选类型
Required
将接口或type定义的对象类型全部变成必选的类型
type t = {name?:string}
let o:t<Required> = {name:'dzp'}//name属性变成必选类型
Pick
从接口或type类型定义中挑选几个属性形成新类型
type t = {name:string,age:number}
let o:t<Pick,'name'> = {name:"dzp"}
Omit
与pick相反,从接口或type定义类型中排除几个属性形成新类型定义
type t = {name:string,age:number}
let o:t<Omit,'name'> = {age:22}
Extract
常用于从联合类型中选择几个类型
type t = string | number
let o:Extract<t,string> = 'name'
Exclude
常用于从联合类型中删除几个类型
type t = string | number
let o:Exclude<t,string> = 22
Record
Record<K,V> 通常k是联合类型作为key存在,而v通常作为接口或者type定义的对象类型约束。
type t = 'name' | 'age'
type t1 = {
title:string
}
let o:Record<t,t1> = {
name:{title:"dzp1"},
age:{title:"dzp2"},
}
Readonly
Readonly 修饰的类型是只读的
interface tss {
name:string,
age:number
}
type t1 = Readonly<tss>
let o:t1 = {name:'dzp',age:22}
o.age = 23//error
十四:+-运算符
+-可以对ts的修饰符进行添加和删除。
+?:没有?的添加修饰符?
-?:有?修饰符的删除?
案例:类型全部变成必选类型
type MyRequired<T> = {
[k in keyof T]-?:T[k]
}
interface P {
name:string
age?:number
}
type r = MyRequired<P>;
let o:r = {name:"dzp",age:22}//age必须填
十五:infer
infer可以对数据类型进行自动推断,infer必须出现在条件类型中才可以使用。infer后面跟的就是自动推导的类型。
使用
在类型的前面加上infer即可
//推断对象类型的属性类型
type get2 = {name:string}
type get3 = get2 extends {name:infer test} ? test:never
案例一:对数组类型的内部类型进行推断
type ArrType<T> = T extends (infer e)[] ? e :never
type Result = ArrType<number[]>
案例二:对函数类型参数进行类型推断
type fn = (name:string) => any
type getFunction<T> = T extends (...args:infer tpye)=>any ? tpye:never
type get1 = getFunction<fn>
十六:declare
declare用来对模块类型进行类型声明,其特点有
-
文件必须是 *.d.ts
-
文件包含的是类型声明,而不是具体实现
-
declare module中模块必须和导入的模块相对应
//index.ts使用lodash模块 import _ from "lodash" //1.d.ts添加lodash类型声明 declare module "lodash" { export xxx }
1. ts项目导入js文件
ts项目直接调用js文件是报错的,会出现警告没有类型声明,因此需要创建.d.ts文件定义声明。 如下所示,通过declare module来为导入的getName方法添加类型声明。
test.js
export function getName(){
return "dzp";
}
index.ts
import {getName} from "./test.js"
getName();
a.d.ts
declare module "*" {
type getName = ()=>string
export {getName}
}
2.为模块添加类型拓展
很多时候项目导入的第三方包提供的声明文件(.d.ts)无法满足我们的类型约束需求,因此我们需要在其基础上添加类型拓展。
vue-router例子
如下是为vue-router提供ts类型约束,约束每个meta参数必须是如下类型
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
declare module 'vue-router' {
interface RouteMeta {
// 是可选的
isAdmin?: boolean
// 每个路由都必须声明
requiresAuth: boolean
}
}
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: HomeView,
meta:{
requiresAuth:true
}
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
meta:{
requiresAuth:true
}
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
在lodash库中添加类型拓展
1.d.ts
declare module "lodash" {
export type dzp=number
}
index.ts:测试使用lodash
import _,{dzp} from "lodash"
let a:dzp = "123"//error
let b:dzp = 123//ok