前言
在 TypeScript 的开发过程中,理解核心类型和工具类型是编写高质量代码的重要基础。本文将深入探讨 TypeScript 中的一些关键概念,首先介绍 interface 和 type 的基本用法与区别,帮助开发者选择合适的声明方式。接下来,我们将讨论一些高级类型,如 keyof、Record、Pick、Partial、Readonly 和 Omit,这些工具类型能够显著提高代码的灵活性与可重用性。
interface
interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。
需要相同里面的属性
定义 对象 类型 如果使用了这个 类型就需要和这个 接口一样
则更适合用于定义对象的结构,以及类的契约。以下是一个使用interface
定义对象结构的示例:
interface 用来 定义 对象结构
type 来 给变量 定义 类型
别名会重合
interface Axxsxs{ name:String } let a:Axxsxs={ name:"youren", }
索引签名
interface Axxsxs { name: String age: number [propName: string]: any } let a: Axxsxs = { name: "youren", age: 12, a: 1, b: 2, } // 只需要 前面的 name,age 符合,后面的 就可以不用管
可选操作符 加上 问号 就可有可无
interface Axxsxs { name: String age?: number } let a: Axxsxs = { name: "youren", }
只读 不可以改
interface Axxsxs { readonly cd: () => boolean readonly :id //id 一般不可改 // cd: () => boolean } let a: Axxsxs = { cd: () => { return false; } } a.cd = () => { return true }
定义函数类型
interface Fn{ (name: string):number[] } const fn:Fn=function(name:string){ return [1] }
当缺少interface 里面的属性的时候会报这个错误
<script setup lang="ts"> interface a { name: string; } interface b extends a{ submit():void; } const youren :b={ name: 'youren', submit() { console.log("submit"); } } console.log(youren) youren.submit() </script>
type
基本使用
//在 TypeScript 中,type 关键字用于创建自定义类型别名。
其作用就是给类型起一个新名字,可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型
主要用于创建联合类型、交叉类型、以及定义复杂的类型别名。下面是一个使用type
定义联合类型的示例:
类型别名
<script setup lang="ts"> // ts 里面 string 中s 用小写 // 定义一个ID 标识符 可以是 number or string type ID = number | string // 表示一个人的 对象 包含 name and age type person = { name:string, age:number } // 表示一个坐标 用 元组表示 type Coordinates = [number, number] // 表示一个回调函数 ,接受一个参数 并返回 void type Callback = (result:any)=>void // 使用 类型别名时 可以直接将别名 作为 类型注解 或 类型 指定 function personID(id:ID){ // 处理标识符 console.log(id) } // 使用元组 function moveTo(coordinates:Coordinates){ // 可以传一个 元组 console.log(coordinates) } function fetchData(callback: Callback) { // 获取数据并调用回调函数 } </script>
联合类型
let phone: number | string=123 console.log(phone)
interface People { name: string, age: number } interface Man { sex: number } const youren = (man: People & Man): void => { console.log(man); } youren({ name: "youren", age: 10, sex: 1 })
断言 as const fn =(type:any):boolean=>{ return type as boolean } let b = fn(1) console.log(b);
any function warnUser(): void { console.log("This is my warning message"); } never // 返回never的函数必须存在无法达到的终点 function error(message: string): never { throw new Error(message); } obj object 表示非原始类型,也就是除 number , string , boolean , bigint , symbol , null 或 undefined 之外的类型。
as let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
// @errors: 2339 interface Bird { fly(): void; layEggs(): void; } interface Fish { swim(): void; layEggs(): void; } declare function getSmallPet(): Fish | Bird; let pet = getSmallPet(); pet.layEggs(); // 只有两种可能类型中的一种可用 pet.swim();
联合 类型
使用联合的一种常用技术是使用字面量类型的单个字段,您可以使用该字段来缩小 TypeScript 可能的当前类型。例如,我们将创建一个包含三种类型的联合,这些类型具有一个共享字段。
type NetworkLoadingState = { state: "loading"; }; type NetworkFailedState = { state: "failed"; code: number; }; type NetworkSuccessState = { state: "success"; response: { title: string; duration: number; summary: string; }; }; // 创建一个只代表上述类型之一的类型,但你还不确定它是哪个。 type NetworkState = | NetworkLoadingState | NetworkFailedState | NetworkSuccessState;
interface ErrorHandling { success: boolean; error?: { message: string }; } interface ArtworksData { artworks: { title: string }[]; } interface ArtistsData { artists: { name: string }[]; } // 这些接口被组合后拥有一致的错误处理,和它们自己的数据 type ArtworksResponse = ArtworksData & ErrorHandling; type ArtistsResponse = ArtistsData & ErrorHandling; const handleArtistsResponse = (response: ArtistsResponse) => { if (response.error) { console.error(response.error.message); return; } console.log(response.artists); };
interface and type
属性容器和对象容器
- 语法:type 使用
type
关键字来定义类型别名,interface 使用interface
关键字来定义接口。 - 功能:type 可以创建任意类型的别名,包括基本类型、联合类型、交叉类型、函数类型等;interface 主要用于定义对象的形状,描述对象的属性和方法。
- 可选属性:interface 可以定义可选属性,即属性名后面加上
?
符号;type 别名不支持可选属性的定义。 - 合并声明:interface 支持合并声明,即可以多次声明同一个接口,编译器会将它们合并为一个接口;type 别名不支持合并声明。
- 实现类:interface 可以被类实现,类可以通过
implements
关键字来实现接口;type 别名不能被类实现。 - 扩展类型:interface 可以通过
extends
关键字来扩展其他接口;type 可以使用交叉类型&
来扩展其他类型。
总体来说,type 主要用于定义类型别名,可以创建任意类型的别名;interface 主要用于定义对象的形状,描述对象的属性和方法。在实际使用中,可以根据具体需求选择使用 type 还是 interface
接口可以扩展,但type不能使用extends和implement,但是type可以通过交叉类型实现interface的extends行为。interface 可以extends type,同时type也可以与interface类型交叉
interface 定义两个会 重合
type 会报错
implement
interface Person{ id:number, } // implements 一个类继承一个接口 class Student implements Person{ // 可以而外的添加,但是原有的必须有 id:number; sex:number } let xiaoming:Student = { id:1, sex:1 } console.log(xiaoming)
extends
继承
继承另一个类
所用时需要 用到 继承的属性
先定义一个大类都可以需要的,然后 都可以 继承他
比如id 大家都要就可以选择 继承 ,
interface Person{ id:number, } // extends 一个接口 扩展一个接口 interface Student extends Person{ sex:number } let xiaoming:Student = { id:1, sex:1 } console.log(xiaoming)
typeof
// typeof操作符用于获取变量的类型,因此操作符后面接的始终是一个变量。 // 假如我们在定义类型之前已经有了对象obj,就可以用typeof来定义一个类型。
将一个对象拥有的属性 提取出来 再得到 一个 新的 类型
const p = { name:"c3", age:19 } type Person = typeof p const p1:Person={ name:"youren", age:10 } console.log(p1) // 等同于 // type Person = { // name:string, // age:number // }
// 如果对象是一个嵌套的对象,typeof也能够正确获取到它们的类型。 const p1 = { name:"c2", age:10, address:{ city:"nanchang" } } type Person = typeof p1 // 等同于 type Person = { name:string, age:number, address:{ city:string } }
export const locales = [ { locale: 'se', language: 'Swedish' }, { locale: 'en', language: 'English' } ] as const; // [number] 为 locale的 字段 type Locale = typeof locales[number]['locale']; // type Locale = "se" | "en" typeof locales 获取了 locales 数组的类型。 locales[number] 通过 [number] 索引访问,TypeScript理解为我们想要获取数组中任意一个元素(即任意索引处的元素)的类型。这是因为 number 在这里作为类型查询的一部分,表示我们正在引用一个数组元素的位置,而不是实际的数字值。 ['locale'] 然后从该元素的类型中访问 locale 属性的类型。
const people = { name: 'liuyz', age: 18, } type INewPeople = typeof people // 等同于 // type INewPeople = { // name: number // age: number // }
const newPeople: INewPeople = { name: "zhi", age: 18, } type TKeys = keyof typeof newPeople // 等同于 // type TKeys = "name" | "age"
keyof
将 type 里面有的 可以分配给小的
// keyof操作符后面接一个类型,生成由string或者number组成的联合字面量类型。 type Person= { name:string, age:number } type PersonKeys= keyof Person const key1 :PersonKeys = 'name' const key2 :PersonKeys = 'age' // Type '"addr"' is not assignable to type 'keyof Person'. const key3: PersonKeys = 'addr';
// 我们希望获取一个对象给定属性名的值,为此, // 我们需要确保我们不会获取 obj 上不存在的属性。所以我们在两个类型之间建立一个约束: const getProperty = <T,k extends keyof T>(obj:T,key:k)=>{ return obj[key] } const person = { name:"c3", age:10 } // 得到了 person 下面的name 属性 console.log(getProperty(person,'name')) // Argument of type '"addr"' is not assignable to parameter of type '"name" | "age"'. console.log(getProperty(person, 'addr'));
keyof T返回T的联合字面量类型,extends用来对K进行约束,表示K为联合字面量类型中的一个。 由于我们使用了类型约束,这样我们在调用getProperty的时候,第二个参数key就必须为第一个参数obj中的属性。在尝试传入不存在的addr属性时 TypeScript 就会报错。
type optionsFlags<T>={ [property in keyof T]:boolean } // use the optionsFlags type FeatureFlags={ darkMode:()=>void; newUserProfile:()=>void } type FeatureOptions = optionsFlags<FeatureFlags> // 等于 type FeatureOptions={ darkMode:boolean, newUserProfile:boolean }
在这个例子中,OptionFlags被定义为类型参数为T的一个泛型,[Property in keyof T]表示T所有属性名的迭代,方括号是索引签名语法。所以,OptionFlags包含T类型的所有属性,并将它们的值重新映射为boolean型。
可以基础方法
type OptionsFlag<T>={ [Property in keyof T]:T[Property] extends Function ? T[Property]:boolean } type Features={ darkMode:()=>void newUserProfile:()=>void } type FeatureOptions = OptionsFlag<Features> // 相当于 // type FeatureOptions = { // darkMode:()=>void // newUserProfile:()=>void // }
type OptionsFlags<T> = { [Property in keyof T]: T[Property] extends Function ? T[Property] : boolean; }; // use the OptionsFlags type FeatureFlags = { darkMode: () => void; newUserProfile: () => void; }; type FeatureOptions = Record<keyof FeatureFlags,boolean >
record
// 可以看到,Record只是将所有属性映射为T类型之后返回的一个新类型。 // 所以我们可以很容易通过Record实现上面映射类型中的例子。 type Record<k extends keyof any,T>={ [p in k]:T }
type TKeys = 'A' | 'B' | 'C' // 类型 interface IPeople { name : string, age ?: number, } type TRecord = Record<TKeys, IPeople> // ABC 都需要,并且有这些 属性 let a : TRecord = { A: { name: "youren", age: 10 }, B: { name: "youren", age: 10 }, C: { name: "youren", age: 10 }, } console.log(a) // 等同于 type TRecord = { B: IPeople; C: IPeople; A: IPeople; }
keyof any: 等同于 string | number | symbol ,也就是说 K 只能是这三种类型 P in K: 指循环 K 类型
pick
//它允许从一个对象类型中选择一个或多个属性,并创建一个新类型。 type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
type Coord = Record<'x' | 'y', number>; type CoordX = Pick<Coord, 'x'>; // 等用于 type CoordX = { x: number; }
- keyof T 获取 T 中所有的 key 属性
- K extends keyof T K 必须继承于 keyof T ,如果 K 中的属性有不属于 keyof T 的则会报错
interface IPeople { name:string, age?: number, sex: string, } // 大 | 小 // 小存在于 大 type TPick = Pick<IPeople, 'name' | 'age'> // 等同于 type TPick = { name: string; age?: number | undefined; }
interface IPeople { name:string, age?: number, sex: string, [key: string]: any } // 可以而外加一些其他的 type TPick = Pick<IPeople, 'name' | 'age' | 'color'> 等同于 type TPick = { name: string; age?: number | undefined; color: any; }
Partial
局部
将类型定义的所有属性都修改为可选。
type Coord = Partial<Record<'x' | 'y', number>>; // 等同于 type Coord = { x?: number; y?: number; }
```Plain Text
interface IUser { name: string age: number department: string } type optional = Partiallet a:optional={ name:"youren" } console.log(a) // optional的结果如下 // type optional = { // name?: string | undefined; // age?: number | undefined; // department?: string | undefined; // } ```
Readonly
type Coord = Readonly<Record<'x' | 'y', number>>; // 等同于 type Coord = { readonly x: number; readonly y: number; } // 如果进行了修改,则会报错: const c: Coord = { x: 1, y: 1 }; c.x = 2; // Error: Cannot assign to 'x' because it is a read-only property.
omit
,它的作用主要是:以一个类型为基础支持剔除某些属性,然后返回一个新类型。
type Person = { name:string, age:string, location:string, } // 接口 | 属性 // 大|小 type PersonWithoutLocation=Omit<Person, 'location'> let a:PersonWithoutLocation={ name:'youren', age:'10', } console.log(a) // 等同于 type Person1={ name:string, age:string }
namesapce
namespace 用来建立一个容器,内部的所有变量和函数,都必须在这个容器里面使用。
先创建一个test.d.ts 文件
//该文件 : typing.d.ts export declare namespace SHOEBOX { //export 该类型 export type Shoe = { size:number name:string } }
然后在项目中引入使用
<script setup lang="ts"> import { Shoe } from './typings' const shoe : Shoe = { size: 1, name: "鞋子" } console.log(shoe) </script>
主要用来区分的
避免命名冲突:通过将代码分组到不同的命名空间中,可以避免不同模块或库之间的命名冲突。
代码组织:namespace 允许你将相关的代码组织在一起,使得代码结构更加清晰。
模块化:在 TypeScript 中,namespace 可以被看作是一种模块化的方式,尽管在 ES6 中通常使用模块(import/export)来实现这一点。
合并声明:在多个文件中,你可以声明同一个 namespace,TypeScript 编译器会自动将它们合并为一个整体。
访问控制:虽然 TypeScript 中没有严格的访问控制,但 namespace 可以作为一种约定,表明某些内容是内部的,不应该在外部直接访问。