类型
在 ts 中,使用 let
var
表示变量,使用 const
表示常量
let a: number = 1
var b: string = "12"
var or let
不过,虽然 let var 都可以表示变量,但是 let 的作用域是小于 var 的,在函数体中,var 表示函数级变量,let 则表示局部变量,所以,ts 中,推荐使用 let 来定义变量,这样可以避免变量的污染。
下面我通过几个例子演示一下
function age(){
{
let a: number = 1
var b: number = 2
}
// 你会发现,a 不可正常执行,但是 b 可以
console.log(a, b)
}
var 的作用域明显是不合理的,这也是 js 中的陋习,let 表示的局部变量更加清晰,也更安全。
let a: number = 1
function age(){
let b: string ="你好"
console.log(a,b)
}
age()
所以,在 ts 中,不要使用 var 作为变量声明,除非你有特殊需求 0
常量
常量就比较常规了:
const PI:number = 3.14
function age(){
console.log(PI)
}
变量初始化
ts 中,类型需要初始化,跟 go 等编程语言,声明就是初始化零值不同,ts 需要手动给予初始化类型
// ts
let a : number = 0
// go
var a int
基础类型
ts 所拥有的基本类型如下:
null
表示空是所有类型的子类型 (当你指定--strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自的类型)
let a: null = null
undefinded
表示未定义
let a: undefined = undefined
string
字符串类型,ts 跟 go 不同,go 有字符类型,ts 不管是单引号还是双引号都只表示字符串的含义,ts 使用反引号 `` 表示字符串模版,go 表示格式输出
//ts
let a: string = '123'
// go ❌,字符只能是单个字符
var b string ='123'
// ts
let a: string = "123"
// go
var b string = "123"
// ts
let age: string = "world"
let a : string = `hello${age}`
// go
var a string = `
d
dfff
`
number
数字类型,ts 中并没有 uint int float 的概念,统一称之为 number,实际上它是浮点数,它存在一定的精度限制和误差,特别是在进行高精度计算或涉及小数部分的操作时。因此,在金融、科学计算等领域,直接使用 number 类型可能会导致数值不精确的问题。对于金融领域需要高精度计算的应用场景,推荐使用专门处理高精度数学运算的库,比如在 TypeScript 中可以使用的 decimal.js 或 big.js 等库,这些库提供了能够准确表示和操作任意精度十进制数字的能力
// 十进制
let a: number = 123
// 十六进制
let a1: number = 0x123
// 八进制
let a2: number = 0o123
// 二进制
let a3: number = 0b101010
// 浮点数
let b: number = 123.456
boolean
布尔类型
let a: boolean = true
let b: boolean = false
bigint
大整数类型
function age(){
let a:bigint = 1299999499494934343443344334433443n
let b:bigint = 443n
console.log("d",a*b)
// ❌ bigint和number不能混用
console.log("d",a*2)
}
age()
symbol
表示独一无二的值
let a: symbol = Symbol()
object
对象,比如数组之类的复杂数据都是对象类型
let a: object = {name: "123"}
void
表示函数返回值为空
function a(): void {
}
never
never 类型表示的是那些永不存在的值的类型,
never 的概念在 go 中并没有被提及,它是所有类型的子类型,也就是说,任何类型都不能赋值给 never 类型
function error(message: string): never {
throw new Error(message)
}
下面这个函数的返回值就是 never,因为它永远都没有返回值
any
any 可以接受任意类型的值,这跟 go 语言的 any 是一个意思
any 和下文提到的 unknown 都属于顶层类型,也就是说能接受所有类型的父类型,这跟 go 的定义也是一样的
表示 any 类型,可以容纳所有值
let a: any = 123
例如这里的 123 会被认为是 number 类型,但是,如果使用了 any 类型,那么,123 会被认为是 any 类型
unknown
any 类型对应的安全类型,unkown 类型的变量只能被赋值给 any 类型和 unknow 类型
let a:unkown
a = 12
a = true
unknown 跟 any 不一样,在操作 unknown 类型的时候,必须进行断言,不断言不能进行操作
let u:unkown = 12
// ❌
u.fool()
// 必须进行断言才可以操作
// ✅
(u as number).fool()
复杂类型
array
数组类型
let a: number[] = [1,2,3]
let b: Array<number> = [1,2,3]
tuple
元组类型
let a: [number, string] = [1, "123"]
let b: [number, string]
b = [1, "123"]
enum
枚举类型
enum Color {
Red= "r",
Blue ="b" ,
Green="g",
}
let c: Color = Color.Red
// 设置 enum 类型初始值,
enum Color1 {
Red = 1,// 依次递增
Blue ,
Green ,
}
let c1: Color1 = Color1.Red
interface a{
v :string
}
interface b {
x:number
}
enum shark {
a,
b,
}
// 这种用法就跟go中的组合一个意思了
Union Types
联合类型
let a: number | string = 123
Intersection Types
交叉类型,这个其实类似于 go 语言中的组合概念
interface part1 {
a:string
}
interface part2 {
b:number
}
type Part = part1 & part2
function age(p:Part){
}
let p:Part = {
a:'hello',
b:18,
}
age(p)
String Literal Types
字符串字面量类型
let a: "123" = "123"
字符串字面量类型和联合类型以及类型别名结合在一起
type b = "124" |"44556"
function age(b1:b){
console.log(b1)
}
age("124")
类型别名
类型别名,给一个类型起另外一个名字
它可以作用于原始值,联合类型,以及交叉类型等任何类型
type aliasBool = boolean
function age():void{
let b:aliasBool = true
}
age()
高级类型
映射类型
在编译时转换已知类型的属性并且创建一个新的类型,可以对已知类型的属性进行转换修改或者添加,比如将属性变成只读类型或者可选类型
type NewType = {
[Property in keyof ExistingType]:TransformType;
}
- NewType 是要创建的新类型
- property 是 ExistingType 的键
- TransformType 是对应属性的转换类型
例如我们看一下实例:
type Readonly<T> = {
readonly [P in keyof T]:T[P];
}
// 接口
interface People {
name:string;
age:number;
}
// 类型别名
type ReadonlyPerson = Readonly<People>
const person:ReadonlyPerson = {
name:"123",
age:123,
}
- Partial 内置映射类型,将属性变成可选
type Partial<T> = { [P in keyof T]?:T[P]; }
- Pick 从执行类型中选择一部分创建新类型
type Pick<T,K extends keyof T> = { [P in K]:T[P]; }
interface Person { name: string; age: number; occupation: string; } type PersonInfo = Pick<Person, "name" | "age">; const info: PersonInfo = { name: "John", age: 30, };
条件类型
T extends U ? X : Y
T 是待检查的类型,U 是条件类型,X 是满足条件时返回的类型,Y 是不满足条件时返回的类型。条件类型通常与泛型一起使用,以便根据不同的类型参数值进行类型推断和转换。
模版字面量类型
type Greeting<T extends string> = `Hello, ${T}!`;
type GreetingWorld = Greeting<'World'>; // GreetingWorld的类型为"Hello, World!"
类型断言
如果想告诉编译器类型推断的意图,我们可以直接断言
let value = "Hello, World!"
let length = (value as string).length
类型判定
T extends U ? x : y
type Check<T> = T extends string ? true : false
判断 t 是否是 string,如果是就返回 true 否则就是 false
属性判断 keyof
interface People {
name: string;
age: number;
}
type PeopleKeys = keyof People
infer
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnValue = ReturnType<typeof add>; // 类型为 number
在上面的示例中,ReturnType 类型接受一个类型参数 T,并使用条件类型和 infer 关键字推断函数类型的返回类型。通过调用 ReturnType
在 TypeScript 中,infer
关键字用于在条件类型中推断类型。它主要用于提取某些复杂类型中的部分类型。
例如,你有一个类型 Foo<T>
,你想提取出 T
的类型。你可以这样做:
type ExtractFoo<T> = T extends Foo<infer U> ? U : never;
这里的 infer U
会尝试推断 Foo<U>
中的 U
的类型,如果 T
可以被赋值为 Foo<U>
的形式,那么 ExtractFoo<T>
就是 U
的类型,否则为 never
。
infer
还可以用于其他场景,比如提取函数返回值类型、提取 Promise 值的类型等。例如:
// 提取函数返回值类型
type FnReturnType<T extends (...args: any[]) => any >= T extends (...args: any[]) => infer R ? R : never;
// 提取Promise值的类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
总的来说,infer
关键字使得在条件类型中推断和提取嵌套类型变得更加方便和清晰。它增强了 TypeScript 的类型系统能力,让我们可以更好地控制和操作复杂的类型。
给出一个案例:
// 定义泛型接口 Foo
interface Foo<T> {
value: T;
}
// 使用 infer 提取 Foo 中的类型 T
type ExtractFoo<T> = T extends Foo<infer U> ? U : never;
// 举例
type FooString = Foo<string>; // { value: string }
type ExtractedString = ExtractFoo<FooString>; // string
type FooNumber = Foo<number>; // { value: number }
type ExtractedNumber = ExtractFoo<FooNumber>; // number
type FooObject = Foo<{ name: string }>; // { value: { name: string } }
type ExtractedObject = ExtractFoo<FooObject>; // { name: string }
// 无法提取的情况
type NotFoo = { value: string }; // 不是 Foo 类型
type FailedExtraction = ExtractFoo<NotFoo>; // never
可以看到实际上是获取的底层的基础数据类型
extends 的含义
在 TypeScript 中,extends
关键字有两个主要用途:
- 用于类继承
在面向对象编程中,extends
用于类继承,允许子类继承父类的属性和方法。例如:
class Animal {
move() {
console.log("Moving...");
}
}
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
let dog = new Dog();
dog.move(); // 输出 "Moving..."
dog.bark(); // 输出 "Woof!"
在这个例子中,Dog
类通过 extends Animal
继承了 Animal
的 move
方法。
- 用于条件类型
在 TypeScript 的条件类型中,extends
用于检查一个类型是否可以赋值给另一个类型。它的语法是:
extendsConstraintType ? TrueType : FalseType
如果类型参数满足约束类型 ConstraintType
,则采用 TrueType
,否则采用 FalseType
。例如:
type IsNumber<T> = T extends number ? "Yes" : "No";
type A = IsNumber<5>; // "Yes"
type B = IsNumber<string>; // "No"
在这个例子中,IsNumber
是一个条件类型,如果传入的类型参数 T
是 number
,则结果是 "Yes"
,否则是 "No"
。
通过条件类型和 extends
关键字,TypeScript 可以根据输入的类型执行不同的逻辑,从而实现更复杂和强大的类型操作。
类型守卫
类型守卫用在运行时检查变量的类型,并缩小变量类型的范围,类型收窄可以让 ts 编译器更好的理解代码的意图
typeof 类型守卫
function printbValue(value:string | number){
// 我们发现其实 value 是联合类型,类型还不够窄,我们可以使用typeof 进一步缩短
if(typeof value === "string"){
console.log(value.length)
}else {
console.log(value)
}
}
instanceof 类型守卫
instanceof 类型守卫,可以用来判断某个值是否为某个类的实例
class Animal {
move(){
console.log("Animal is moving")
}
}
class Dog extends Animal {
bark(){
console.log("Dog is barking")
}
}
func printDogValue(animal:Animal){
if (animal instanceof Dog){
animal.bark()
}else {
animal.move()
}
}
自定义谓词函数类型守卫
interface Circle{
kind:'Circle';
radius: number;
}
interface Rectangle{
kind:'Rectangle';
}
type Shaple = Circle | Rectangle
function calculateArea(shape: Shape) {
if (isCircle(shape)) {
console.log(Math.PI * shape.radius ** 2);
} else {
console.log(shape.width * shape.height);
}
}
// 其中这一句 shape is Circle 是一个断言,也是告诉编译器我们要检查的是shap的类型
//TypeScript 编译器会根据这个返回值类型断言推断调用此函数后的变量就是 Circle 类型,
// 增强了编译时类型检查的安全性。
function isCircle(shape: Shape): shape is Circle {
return shape.kind === 'circle';
}
联合类型守卫
这个跟 go 的 switch 断言语句类似
interface Car {
type: 'car';
brand: string;
wheels: number;
}
interface Bicycle {
type: 'bicycle';
color: string;
}
interface Motorcycle {
type: 'motorcycle';
engine: number;
}
type Vehicle = Car | Bicycle | Motorcycle;
function printVehicleInfo(vehicle: Vehicle) {
switch (vehicle.type) {
case 'car':
console.log(`Brand: ${vehicle.brand}, Wheels: ${vehicle.wheels}`);
break;
case 'bicycle':
console.log(`Color: ${vehicle.color}`);
break;
case 'motorcycle':
console.log(`Engine: ${vehicle.engine}`);
break;
default:
const _exhaustiveCheck: never = vehicle;
}
}
in 操作符进行类型守卫
in 判断某个属性是否在某个类型中
interface Circle {
kind: 'circle';
radius: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
type Shape = Circle | Rectangle;
function printArea(shape: Shape) {
if ('radius' in shape) {
console.log(Math.PI * shape.radius ** 2);
} else {
console.log(shape.width * shape.height);
}
}
真值类型守卫
当条件表达式的结果是 true 的时候,收窄类型
function isV(value:string|null){
if (value){
console.log(value.length)
}else{
console.log('value is null')
}
}
自定义类型判断守卫
interface Bird {
fly(): void;
}
interface Fish {
swim(): void;
}
// 最后的一句是断言语句,同时它也是一个布尔类型
function isBird(animal: Bird | Fish): animal is Bird {
return (animal as Bird).fly !== undefined;
}
参考资料
- https://typescriptlang.org
- https://www.coding-time.cn/