TypeScript 程序员晋级的 11 个必备技巧( 三 )

有个错误,这是因为根据circle?的声明类型NamedCircle,name?字段确实可以未定义,即使变量初始值设定项提供了字符串值 。当然,我们可以删除:NamedCircle?类型注释,但这将松散对circle对象有效性的类型检查 。进退两难 。
幸运的是,Typescript 4.9引入了一个新的satisfies关键字,它允许你在不更改推断类型的情况下检查类型:
type NamedCircle = {radius: number;name?: string;};// error because radius violates NamedCircleconst wrongCircle = { radius: '1.0', name: 'ha' }satisfies NamedCircle;const circle = { radius: 1.0, name: 'yeah' }satisfies NamedCircle;// circle.name can't be undefined nowconsole.log(circle.name.length);修改后的版本具有两个优点:对象字面量保证符合NamedCircle类型,推断类型具有不可为空的名称字段 。
#10 使用infer创建额外的泛型类型参数在设计实用工具函数和类型时,你经常会觉得需要使用从给定类型参数中提取的类型 。在这种情况下,infer关键字就可以派上用场 。它可以帮助快速推断新的类型参数 。下面是两个简单的例子:
// gets the unwrApped type out of a Promise;// idempotent if T is not Promisetype ResolvedPromise<T> = T extends Promise<infer U> ? U : T;type t = ResolvedPromise<Promise<string>>; // t: string// gets the flattened type of array T;// idempotent if T is not arraytype Flatten<T> = T extends Array<infer E> ? Flatten<E> : T;type e = Flatten<number[][]>; // e: numberinfer?关键字在T extends Promise<infer U>?中的工作原理可以理解为:假设T?与一些实例化的泛型Promise类型兼容,临时凑合一个类型参数U?以使其工作 。因此,如果T?被实例化为Promise<string>?,则U?的解决方案将是string 。
#11 创新类型操作以保持DRYTypeScript提供了强大的类型操作语法和一组非常有用的实用程序,可帮助你将代码重复减少到最低限度 。以下是一些简单示例:
与其重复字段声明:
type User = {age: number;gender: string;country: string;city: string};type Demographic = { age: number: gender: string; };type Geo = { country: string; city: string; };还不如使用pick实用程序提取新类型:
type User = {age: number;gender: string;country: string;city: string};type Demographic = Pick<User, 'age'|'gender'>;type Geo = Pick<User, 'country'|'city'>;与其复制函数的返回类型:
function createCircle() {return {kind: 'circle' as const,radius: 1.0}}function transformCircle(circle: { kind: 'circle'; radius: number }) {...}transformCircle(createCircle());还不如使用ReturnType<T>提取:
function createCircle() {return {kind: 'circle' as const,radius: 1.0}}function transformCircle(circle: ReturnType<typeof createCircle>) {...}transformCircle(createCircle());与其并行同步两种类型的shape?(此处为config?类型和Factory):type ContentTypes = 'news' | 'blog' | 'video';// config for indicating what content types are enabledconst config = { news: true, blog: true, video: false }satisfies Record<ContentTypes, boolean>;// factory for creating contentstype Factory = {createNews: () => Content;createBlog: () => Content;};还不如使用映射类型和模板字面量类型根据config?的形状自动推断正确的factory类型:
type ContentTypes = 'news' | 'blog' | 'video';// generic factory type with a inferred list of methods// based on the shape of the given Configtype ContentFactory<Config extends Record<ContentTypes, boolean>> = {[k in string & keyof Config as Config[k] extends true? `create${Capitalize<k>}`: never]: () => Content;};// config for indicating what content types are enabledconst config = { news: true, blog: true, video: false }satisfies Record<ContentTypes, boolean>;type Factory = ContentFactory<typeof config>;// Factory: {//createNews: () => Content;//createBlog: () => Content; // }总结这篇文章介绍了一系列TypeScript语言的高级应用 。在实践中,你可能会发现直接这样用并不常见;但是,这些技术被大量用于那些专门为TypeScript而设计的库:如Prisma和tRPC 。了解这些技巧可以帮助你更好地理解这些工具是发挥其威力的 。

【TypeScript 程序员晋级的 11 个必备技巧】


推荐阅读