Typescript 稍稍入个门
TypeScript 介绍
TypeScript 是一种由微软开发的自由和开源的编程语言。在对 JavaScript 结构进行静态分析时很有可能是错误,为了防止并排查 JavaScript 运行时的错误,微软创造了一种在编译时进行静态类型分析强类型语言 -- TypeScript。
TypeScript 不仅是 JavaScript 的超集,在兼容 JavaScript 的同时向 TypeScript 中添加了基于类的对象、接口和模块等面向对象特性,并支持跨平台开发,可以在所有主流的操作系统上安装和执行,让代码更具维护性与扩展性,能够帮助我们写出更优雅的代码。
TypeScript 的特性:
- 编译时类型检查:提供静态类型检查,在编译时能发现潜在的类型错误,避免在运行时出现报错。
- 面向对象编程:支持面向对象编程,包括类、接口、继承、抽象类等特性。
- 类型注解:支持类型注解,可以明确地指定变量、函数的类型,提高代码可读性和可维护性。
- ES6/ES7 的支持:支持 ES6/ES7 的新特性,如箭头函数、解构赋值、async/await 等。
- 命名空间和模块化:支持命名空间和模块化,可以更好地组织代码。
- 工具支持:TypeScript 集成了许多开发工具,包括编辑器、调试器、构建工具等,可以提高开发效率。
TypeScript 的安装
在开发环境中安装 Node.js 后我们可以使用 npm 命令安装 typescript
全局安装 TypeScript
npm i -g typescript@4.5.2
// 查看 typescript版本类型
tsc -v
(git bash 是按装了 git 之后才有的)
在项目文件夹中初始化项目
tsc -init
会多一个 TypeScript 配置文件 tsconfig.json
新建 ts 文件 01.ts 编译 ts
tsc 01.ts
其中 tsc 是 TypeScript 编译器的控制台接口,这个命令可以将 ts 编译成 js文件,如果没有以外的话可以在 01.ts 的相同目录下找到一个 01.js 的文件。
会输出一个新的 js 文件,因为在运行 ts 文件时会被编译成 js文件
可以在 tsconfig.json 中发现编译成 js 的位置
ts 在定义时 类型已经确定
ts 中 let 和 const的区别: let的类型由值来决定 const的值就是类型
ts 中的基本类型
先来看一下 ts 中是如何申明类型的:
let counter; // 未知为 any 类型
let counter = 0; // number (number类型被自动推测出来,通过0 能判断出是number类型,该过程被称为类型推导)
let counter:number ; // number
let counter:number = 1 ; // number
这些原始类型在 ts 中都有对应的类型注解,可以用来明确指定变量或函数的类型。
变量的类型声明在变量名后的这种风格是基于类型理论,且更强调类型是可选的,在没有声明类型的情况下,ts 会尝试检查赋值给变量的值来推测变量的类型。
当一个变量的类型无法被推导时,一个特殊的类型 any 会作为他的类型。
js 中的原始类型: string、number、boolean、symbol、undefined、null
ts 的原始类型在包含 js 原始类型的基础上多了 void 和用户自定义的 enum类型等基本类型。
所有的类型在 ts 中都是一个唯一的顶层的 Any Type 类型的子类型,any 关键字代表这种类型
let myStr:string = '1'
let bool:boolean = true
let und:undefined = undefined
let myNum:number = 2
let sy:symbol = Symbol('123')
let nul:null = null
let vo:void = undefined // 函数
void
void 表示没有任何返回值的函数的返回类型,或者变量的类型。
function a(){} // function a():void 在ts 中函数没有返回值,函数的类型就是 void
function b():void {}
function c():undefined{return undefined}
enum
枚举类型是为了给一个数组集合更友好的命名,枚举类型可以更加清晰地表达代码的意图,可以用于定义一组有名字的常量,使代码更具可读性和可维护性。
// 枚举类型,用于定义一组有名字的常量
enum Color {Red, Green, Black}
let color:Color = Color.Green // 1
// 枚举数值默认从0开始依次递增
默认情况下,从 0 开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将 http 的请求状态设置为对应的 code 值:success 设置为 200,error 设为 400,serveError 则为 500。
export {}
//枚举 不是用来定义类型,用于列举数据
enum status{
success = 200,
error = 400,
serveError = 500
}
const code:string|number = 400
if(code === status.success){
console.log('success')
} else if(code === status.error) {
console.log('error')
} else if(code === status.serveError){
console.log('serveError')
}
或者,全部都采用手动赋值:
// 若是枚举中没有赋值 则默认从 0 开始递增赋值
enum statusCode{
success, // 0
error = 400, // 400
serveError // 401
}
ts中的非原始类型
在 ts 中,非原始类型指的是除了原始类型(number、string、boolean、null、undefined、symbol)之外的其他类型
对象类型
Object 定义了一个只能保存对象的变量,是一个非原始类型。它是所有对象的基类。它包含了一些常用的属性和方法,也可以使用接口(interface)来定义。
export {} //模块化,防止当前文件的变量全局化,实现文件之间的数据隔离
// ts 中的非原始类型 : object、 Object、 {}
// object 不包含基础类型,用于区分类型
let obj:object = {a:1}
let arr:object = [1,2]
// let bool:object = true //报错
// let num:object = 11 //报错
// Object 包含基础数据类型
let obj1:Object = {a:1}
let arr1:Object = [1,2]
let bool:Object = true
let num1:Object = 1
// {} 等效于Object,包含基础数据类型
let obj2:{} = {a:1}
let arr2:{} = [1,2]
let boo2:{} = true
let num2:{} = 1
数组
在 ts 中可以 像 js 一样可以操作数组元素。 有两种方式可以定义数组:
- 可以在元素类型后面接上[],表示由此类型元素组成的一个数组
- 使用数组泛型,Array<元素类型>:
// 数组中元素的数据类型都一致便于后续数据的处理
// 数组的第一种写法:直接拼接 []
let arr:number[] = [1,2]
arr[0]=10
// arr[1]='123'
// 第二种写法: 泛型 类型参数化 (将数据类型作为参数)
let arr2:Array<number>= [1,2,3]
元组
元组类型( Tuple )允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string 和 number 类型的元组。
// 元组 其中的数据不一定是相同类型,每一项的数据类型被定死
let arr3:[number,number,boolean] = [1,2,true]
联合类型(|)
所谓的联合类型指的是一个值可以是多种类型中的一种,可以用联合类型(T1 | T2 | ...)来定义。
其中或操作符 |
只要满足一个条件即可,或者应用在所有条件都满足的情况下。
// | 联合类型 也可以理解为 或者
let numberStr:number|string = 10
numberStr = 'abc'
let num: 1|'2'=1 // 1 和 '2' 是类型 常量,表示num 的值只能是 1 或 '2'
let obj:{a:'1'}|{b:'3'} // 声明类型
obj = { a:'1' }
obj = { b:'3' }
obj = { a:'1',b:'3' } // obj 中的 | 表示要么有 a 属性 要么有 b 属性 或者都有
// obj = {a:'1',b:'3', c:1} // c 属性 报错
交叉类型 (&)
在表示多个类型的结合时可以使用交叉类型,&
操作符要求所有的条件必须同时满足
// & 交叉类型 同时满足两个条件
let a:number&string // 不会有任何值能够在满足 number 的情况下又是一个 strinmg
let obj:{name:string,age:number} & {height:number} // & 必须都得满足
obj={name: 'Gina',age:18,height: 168}
any & unknow
接下来认识两个 TypeScript 中的顶级类型,他们都能够用来表示动态类型或未知类型。
- any: 表示任意类型,可以被赋值为任意类型,也可以赋值给任意类型。使用 any 类型会关闭 TypeScript 的类型检查,因此不建议滥用。
- unknown: 表示未知类型,它类似于 any,但是比 any 更安全。使用 unknown 类型时,必须进行类型检查或类型断言才能将其赋值给其他类型。这可以避免在运行时出现类型错误。
// any
let number:any // any 绕过类型校验
number = '10'
number = 10
number ={b:10}
number.toFixed(2) // 绕过类型校验 不会在编译时报错
let n:unknown
n = 1
n = '10'
n= [10]
// n.toFixed(2) // 报错 会进行类型的校验, 除非确定 变量类型为 number 才不会报错
// 通过 typeof 进行类型的校验,实现了类型守护
if(typeof n ==='number'){
n.toFixed(2)
} else if( typeof n ==='string'){
n.concat()
}
any 和 unknown 的区别:
- any 可以被赋值为任意类型,而 unknown 必须进行类型检查或类型断言才能赋值为其他类型。
- any 关闭了 TypeScript 的类型检查,而 unknown 更安全,可以避免在运行时出现类型错误。
interface
当我们需要表示一个对象的结构,包括属性、方法、索引等,可以使用接口(interface)来定义。
// interface 用于 自定义类型
// 定义接口类型 --- 对象
interface MyInterface{
// 属性名: 值的类型
name: string,
age: number,
}
// 用接口来校验对象
let me: MyInterface ={
name:'Gina',
age: 18
}
// 定义接口类型 --- 数组
interface Arr {
// [index:number] 下标类型: 值类型
[index:number]: number|string
}
let arr = [1,2,3,'4']
// 定义接口类型 --- 函数
interface FnIn{
// 形参及类型: 返回值类型
(p:string): void
}
let fn:FnIn = (p:string)=>{
}
fn("") // 若不传参则会报错
T我们可以将接口理解为是对象的状态(属性)和行为(方法)的抽象(描述)。
继承 同名 缺省
- 继承:使用 extends 关键字来实现继承。子类可以继承父类的属性和方法,并且可以通过 super 关键字调用父类的构造函数和方法。
- 同名:如果子类和父类拥有同名的属性或方法,子类会覆盖父类的同名属性或方法。如果需要在子类中调用父类的同名属性或方法,可以使用 super 关键字。
- 缺省:在 TypeScript 中,可以使用 ? 符号来表示一个属性或方法是可选的。如果一个对象没有某个可选属性或方法,访问它不会产生运行时错误。
interface NameInter{
readonly name:string // readonly 修饰符 表示只读属性, 只允许读取,不支持修改,否则会报错
}
interface AgeInter{
age?:number // 属性名? 表示这个属性 可以缺省 (定义数据时该属性可有可无)
}
// 接口继承的格式, extends 特点: 可以继承父接口的所有属性,且可以同时继承自多个不同的接口
interface PersonInter extends NameInter,AgeInter{
height:number
}
// 必须同时拥有 继承下来的属性,少写一个都会报错
let person:PersonInter
person = {
name:'Gina',
age:18,
height: 168
}
// 接口可以同名 特点:会合并同名接口的所有属性
interface AInter{
a: number
}
interface AInter{
b: number
}
// a、b 属性都得有,少写一个都会报错
let obj:AInter ={
a:1,
b:2
}
**接口有一个特点就是当有接口同名时会将属性进行拓展:**如果一个接口继承自另一个接口,它会拥有另一个接口的所有属性和方法,并且可以添加自己的属性和方法。
联合交叉类型
在 TypeScript 中,可以使用联合类型和交叉类型来组合多个类型,联合交叉类型即为联合类型和交叉类型的组合使用。
// && 的优先级大于 ||
console.log( 1||2&&3) // 1(2&&3) ===>>> 1
// 联合交叉类型 ( | & )
// & 优先于 |
let obj:{name:string} & {age:number} | {name:number} & {age:string}
// obj 要么完全符合前者 {name:string} & {age:number}
obj = {name :'Gina',age:18}
// 要么完全符合后者 {name:number} & {age:string}
obj = {name :20,age:'18'}
type 类型(类型别名)
使用 type 关键字来创建类型别名,用于给一个类型起一个新的名字。类型别名可以用于简化复杂类型的定义,提高代码可读性和可维护性。
export{}
// type 自定义类型 (类型别名)
type numOrStr = number|string
let str:numOrStr = 10
str = 'haha'
// 给对象定义类型
type ObjType = {a:number&2,b:string}
// type ObjType = {c:number} // type 同名会报错
let obj:ObjType={
a:2,
b:'123'
}
// type 和 interface 的区别
// 共同点 都可以用来自定义类型
// 区别: interface 可以重复命名,
// type 不支持重复定义,支持联合交叉类型定义
ts 函数
在 ts 中可以对函数的类型进行定义,包括参数类型和返回值类型,可以使用函数类型((arg1: T1, arg2: T2, ...) => R)来定义。
// (a:形参类型):return的返回类型
function fn(a:number,b:number):number{
return a + b
}
// 接口定义函数类型
interface FnTnter{
(p:string):number
}
let fn1:FnTnter = (p:string)=>{
return 1
}
fn1('')
// 类型别名定义函数类型
type Fntype = (p:string)=>void
let fn2:Fntype = (p:string):void =>{}
fn2('')
// 函数 作为对象的属性出现
// interface
interface ObjFnInter{
fn: FnTnter
}
let obj1:ObjFnInter = {
fn:(string)=>{
return 1
}
}
obj1.fn('')
// type
type ObjType = { fn:(p:string)=>number}
let obj2:ObjType = {
fn:(str)=>{
return 1
}
}
obj2.fn('2')
ts 函数参数
// 默认参数 参数名:number=3 这个参数的默认值是3
function fn(a:number , b:number=3){
return a+b
}
console.log(fn(1,2));
console.log(fn(5));
console.log("--------")
// 缺省参数参数名? 表示可以被缺省的参数
function fn1(a:number , b?:number){
return 1
}
fn1(1,2);
fn1(1);
// 剩余参数
function fn2(a:number , b:number, ...arr:number[]){
console.log(a,b);
console.log(arr);
}
fn2(1,2,3,5,4)
// let arr1 = [1,2,3];
// let arr2 = [...arr1];
// arr1[0] = 4;
// console.log(arr1);
// console.log(arr2);
let obj1 = {a:1,b:2,c:[1,2,3]3}
let obj2 = {...obj1} //浅拷贝
obj2.a = 3
ts 中的 Promise
在 ts 中,也可以像 js 一样使用 Promise 类型来处理异步操作。
Promise 是一种表示异步操作结果的对象,它可以在异步操作完成时返回一个值或抛出一个异常。
interface DataItff{
a:number;
b:number;
}
interface ResItff{
code:number;
data: DataItf[]; // 对象数组的形式 {a:number,b:number}[];
message:string;
}
// promise对象: p对象名:Promise<res的类型>
let p:Promise<ResItf> = new Promise((resolve,reject)=>(
resolve({
code:0,
data: [(a:1,b:2),(a:11,b:22)]
message:''
})
})
p.then(res=>{
if(res.code==0){
res.data.map(item=>item.a)
}
})
ts 中的this 指向
在浏览器环境中,this 来源于默认值 window, window 就是 Window 的实例对象, 而函数中的 this 指向可以使用箭头函数或 Function.prototype.bind() 方法来指定。
// this 指向
// 在全局上给window接口扩展 当 接口同名时会合并属性 进行扩展
interface Window {
myName:string
}
// ts 提供了 Window 类型, window 就是Window 类型的对象
function Person(this:Window,name:string){
// ts 的书写中 需要指明 this 指向,在函数的第一个形参位置注明
// 当 Window 类型中没有 myName 这个属性时需要自己扩展
this.myName = name
}
window.Person('haha')
要拓展 Window 属性必须在全局下注册,那如果将该文件设置为私有的作用域时会发生什么呢?
该文件将不再是全局环境,Person() 将不属于window,将 Window 的接口拓展提取到全局的文件下,防止文件间的隔离
interface Window {
Person:(n:string)=>void
myName:string
}
当设置了 export{} 后又会发生什么呢
export {}
// 当设置了 export{} 后 这里就不是全局,在这里扩展的 Window 接口失效,需要写到 global.d.ts 的全局作用域下
// ts 提供了 Window 类型, window 就是Window 类型的对象
function Person(this:Window,name:string){
// ts 的书写中 需要指明 this 指向,在函数的第一个形参位置注明
// 当 Window 类型中没有 myName 这个属性时需要自己扩展
this.myName = name
}
// 将Person 函数注册到 window 的 Person 属性上
// 加了 export{} 后这里就不是全局,Person 不是 window 下的属性
window.Person = Person
window.Person('haha')
this 指向自定义对象
this 关键字引用当前对象。如果需要在自定义对象中使用 this,可以使用箭头函数或者在方法中使用 this。
export{}
type objType={
name: string,
Person:(n:string)=>void
}
let obj:objType ={
name:'haha',
Person:()=>{}
}
// 定义函数的时候 this 的类型必须和调用函数时的类型保持一致
// this: Window|ObjType 可以给多种类型所对应的对象 让 this 去指向
function Person(this:objType,name:string) {
this.name = name
}
obj.Person = Person
obj.Person('123')
泛型(接口、类型别名)
泛型 指的是 类型参数化,将原来某种具体的类型进行参数化
// 泛型 将类型作为参数传入,
// T 是一个标识符 可以自定义,T 表示一种类型
// 类型参数化
function fn<T>(n:T):T{
return n
}
fn<number>(100)
fn<string>('123')
// 将值作为类型传入,那么传入的 参数只能是值得本身
fn<'hello'>('hello')
function fn1<T,G>(n:T,m:G):T{
return n
}
fn1<string,number>('123',100)
fn1<boolean,number>(true,100)
// 通过泛型声明数组
let arr:Array<number> = [1]
通过 类型别名、接口声明泛型
// 泛型 -- 类型别名
type StrOrNumber = string | number
type ObjType<N,G> = { name: N, getName:()=>G}
let obj:ObjType<StrOrNumber,StrOrNumber> = {
name: 123,
getName(){
return 1
}
}
// 泛型 -- 接口
// 可以设置默认类型,在使用时可以省略不传
interface ObjInter<T,G = number> {
name: T,
getName:()=>G
}
let obj2:ObjInter<StrOrNumber> = {
name: 'yiyi',
getName() {
return 123
},
}
泛型约束 (extends)
通过泛型约束可以用来限制泛型类型参数的类型范围。泛型约束使用 extends 关键字来指定所允许的类型。
type StrOrNumber = string | number
// extends 设置泛型只能接收的类型
interface ObjInter<N extends StrOrNumber,G>{
name: N, // N 只能接收字符串 或者数字
getName: ()=>G
}
let obj:ObjInter<number,string> ={
name:1,
getName() {
return '123'
},
}
[]动态取值
在 Vue 3 和 TypeScript 中,可以通过 [ ] 动态访问属性名。假设有一个对象 obj,可以使用以下代码访问其动态属性:
typescript复制代码const obj = { foo: 'bar' };
const prop = 'foo';
console.log(obj[prop]); // 输出 'bar'
注意:由于 TypeScript 的限制,可能需要将对象类型声明为 { [key: string]: any },以确保可以使用动态属性名。
例如,可以这样声明上面的 obj 对象:
const obj: { [key: string]: any } = { foo: 'bar' };
类
TypeScript 中的 class 是一种面向对象的编程结构,它允许封装数据和方法,并将它们组织成一个可重用的代码块。类是创建对象的蓝图,其中包含了对象的属性和方法。
在 TypeScript 中,类可以包含构造函数、属性、方法和访问修饰符等。
class Person{
myName: string
constructor(n:string) {
this.myName = n
}
getName() {
return this.myName
}
}
class Male extends Person {
age:number
constructor(name:string,age:number){
super(name) // 传参调用父类的 constructor
this.age = age
}
// 重写(子类的 方法名与父类同名,会重写这个方法,在调用时会优先调用子类的同名方法)
getName() {
return 'my name is ' + this.myName
}
}
let me = new Male('yiyi',18)
console.log(me.myName)
console.log(me.getName())
类的修饰符
类里面 定义的属性 默认的修饰符是public
public 修饰的属性 方法 都可以在类的内部、类的外部、子类中访问 protected 受保护 在类的内部、子类中被访问 (类外部不能访问) private 私有的 只能在类的内部被访问, (子类和类的外部都不能) readonly 设置属性只读, 不能被修改 **static 静态成员 (静态属性) **供类使用
抽象类
抽象类是 TypeScript 中的一种特殊类,它不能直接被实例化,而是用于作为其他类的基类。
抽象类可以包含抽象方法,这些方法只有定义,而没有具体实现。
子类必须实现这些抽象方法,否则会导致编译错误。
注意:抽象类无法被实例化 !!!
// 抽象类 对普通类的描述 ,指定了一个类的规范, 给普通类去继承
// 继承之后 普通类里就必须包含抽象类中的抽象属性 和 抽象方法
// 抽象类中的普通方法直接继承,在普通类中不用实现
abstract class Person {
abstract name: string
abstract getName():string
getAge(){
return 18
}
}
// 普通类继承自 抽象类
class Male extends Person {
name:string = ''
getName() {
return this.name
}
}
let person = new Male()
console.log(person.getAge())
// 抽象类无法被实例化 !!!
let p1 = new Person() // 报错
抽象类中的普通方法可以被子类直接继承,而不需要在子类中实现这些方法。
这是因为抽象类中的普通方法已经有了具体的实现,子类可以直接使用这些实现。
通过接口 规范类 在 TypeScript 中,可以使用接口来规范类的结构。接口定义了一个类应该具有哪些属性和方法,但不提供具体的实现。类可以实现一个或多个接口,从而满足接口定义的要求。
// 接口 给类使用 规定了 类的属性和方法
interface PerInter {
name: string,
age?: number, // 缺省
getName:()=> void
}
class Female implements PerInter {
name:string = ''
age:number = 18
getName(){
}
}
工具类型
Partial
Partial 设置接口中的属性为可缺省属性
interface PerInter {
name:string
age:number
}
// Partial 把 <> 里得接口类型的属性设置为可缺省的属性
let obj:Partial<PerInter>={
name:''
}
// Partial 相当于 type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
keyof T 'name'|'age'
{
name?:string|undefined
age?:number|undefined
}
Required
Required 设置接口中的属性为必填属性
interface PerInter {
name:string
age:number
height?:number
}
// Required 把 <> 里得接口类型得属性设置为必填属性
let obj2:Required<PerInter>={
name: 'haha',
age: 12,
height: 168
}
// Required type Required<T> = { [P in keyof T]-?: T[P]; }
// keyof T 'name'|'age'|'height'
// -? 抵消? 即 去掉 缺省 变成必填属性
// {
// name:string
// age:number
// height:number
// }
总结
以上是一些关于 TypeScript 类型的基本内容, 想要进阶学习 ts 的小伙伴可以看看这几个优秀的开源项目,这也是我前两天刚找到的😅,大家一起学习共同进步啊:
网站 | 说明 |
---|---|
TypeScript | TypeScript 官网,TS 扩展了 JavaScript ,为它添加了类型支持 |
TypeScript-tutorial | TypeScript 入门教程,循序渐进的理解 TypeScript |
TypeScript | TypeScript 使用手册 |
TypeScript-book-chinese | 深入理解 TypeScript |
clean-code-TypeScript | 适用于TypeScript的简洁代码概念 |
TypeScript入门 | TypeScript 入门的视频教程 |
TypeScript-tutorial | TypeScript 速成教程(2小时速成) |
转载自:https://juejin.cn/post/7248199693934739512