头图

typescript 作为前端使用最多的框架之一,快来看看下面这些隐藏的高级技巧吧

Mapped Types

当我们在声明类型的时候,可以借助 mapped types">Mapped Types 的方式对基础类进行扩展,这样就可以减少定义重复的基础类型

type Ev = {
    add: string;
    remove: string;
    move: string;
}

type OnEvents = {
    [k in keyof Ev as `on${Capitalize<Exclude<k, 'move'>>}`]: () => void
}

const userActions: OnEvents = {
    onAdd: () => { },
    onRemove: () => { },
}

上面的类型声明利用了 keyof 对原始的 基础类型 Ev 进行遍历形成行的 OnEvents 类型,这个类型的 key 的成员就是 Ev 类型中的 基础类型,同时这里使用了 as 对 重新的 map 的类型进行新的定义

as `on${Capitalize<Exclude<k, 'move'>>}`

我们可以将 key 的声明单独拎出来看,上面使用了 Excludemove 字段排除,并且通过 Capitalize 工具类将剩余的 key 转换成 onAddonRemove

Template literals

Template literals 模板字面类型通过 模板字符串语法,借以字符串字面类型为基础,并能通过联合扩展到许多字符串

type GapType = "margin" | "padding"
type GapDirection = "top" | "right" | "bottom" | "left"

type GapCss = `${GapType}-${GapDirection}`

type SizeType = 'rem' | 'px' | '%';
type SizeCss = `${number}${SizeType}`
type MarginPadding = {
    [Key in GapCss]?: SizeCss
}
const margin: MarginPadding = {
    'margin-left': '1rem'
}

Never type

Never Type">Never Type 类型表示的是那些永不存在的值的类型。它是任何类型的子类型,但没有类型是 never 的子类型(除了 never 本身)。never 类型常用于表示不可能发生的事情,例如一个函数永远不会正常返回,或者一个变量将永远不会有值.

type NoEmptyString<T> = T extends '' ? never : T;

function failOnEmptyString<T extends string>(input: NoEmptyString<T>) {
    if (!input.length) {
        throw new Error('input is empty');
    }
}

failOnEmptyString('');

所以上面的案例通过 never 类型进行限定,表示 failOnEmptyString 函数永远不能输入空的字符串

satisfies 操作符

satisfies 在 Typescript 4.9+ 版本才支持

TypeScript 中的 satisfies">satisfies 操作符用于断言特定对象满足特定类型。这与使用类型断言(如 as)不同,因为 satisfies 不会改变变量的类型;它只是检查变量是否与指定类型兼容。如果类型检查失败,代码将无法编译,因此它是一种编译时特性。

type Colors = "red" | "green" | "blue";

type RGB = [red: number, green: number, blue: number];

const palette: Record<Colors, string | RGB> = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
};

const red = palette.red.find(v => v === 255)

相信上面的报错在日常开发中,经常会遇到的,平常的解决方法大多数都是 使用 as 大法, 但是有时候 as 大法也不是很好用。 用 satisfies 就可以很好解决这种情况

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>;

infer

在 TypeScript 中,"infer"关键字是一个强大的功能,可用于Conditional Types条件类型的上下文中,根据使用它们的上下文自动推断类型。这可以使类型实用程序更加灵活,能够处理复杂的类型场景,而无需用户进行显式类型声明。

type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;
infer 关键字必须使用在「条件类型的子句」,也就是 extends 后面 ? 前面的位置

infer 的一些使用场景

  • 推断对象的值类型
type ObjVal<T> = T extends { x: infer U; } ? U : never;
const a: ObjVal<{ x: string }> = '2323'
  • 推断函数的参数类型

    type FuncParamType<T> = T extends (x: infer U) => any ? U : never;
    let aa: FuncParamType<(age: number) => false> = 2432
    let aa1: FuncParamType<(age: number) => false> = '2432'

  • 推断 Generice 参数的类型

    type PromiseType<T> = T extends Promise<infer U> ? U : never;
    let b: PromiseType<Promise<boolean>> = false
    let bb: PromiseType<boolean> = false

  • 推断 String 的一部分

    type RemoveUnderscore<T> = T extends `_${infer R}` ? R : T;
    let cc: RemoveUnderscore<number> = 2323
    let cc1: RemoveUnderscore<string> = `_I have underscore`
    只有以 _ 开头的字符串才会被推断成 R 类型

Type Guard 类型守卫

TypeScript 中,类型守卫(Type Guards)是一种技术或模式,它使你能够在编译时期确定一个变量的类型。这种机制对于在运行时根据不同的类型执行不同的操作非常有用,尤其是在处理联合类型或更复杂的类型结构时。

在 TS 中,我们可以使用 typeofinstanceOfis 等操作符来定义类型守卫

typeof

function padLeft(value: string | string[], padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

console.log(padLeft("Hello, world", 4));
console.log(padLeft("Hello, world", "John says "));

instanceOf

class Bird {
    fly() {}
}

class Fish {
    swim() {}
}

function move(pet: Bird | Fish) {
    if (pet instanceof Bird) {
        pet.fly();
    } else if (pet instanceof Fish) {
        pet.swim();
    }
}

const myBird = new Bird();
const myFish = new Fish();

move(myBird);
move(myFish);

is

利用类型谓词 parameter is Type 来确定变量的类型

interface Bird {
    fly(): void;
    layEggs(): void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

function isFish(pet: Bird | Fish): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

function getPetAction(pet: Bird | Fish) {
    if (isFish(pet)) {
        pet.swim();
    } else {
        pet.fly();
    }
}

const pet = getPetAction({ fly: () => console.log("fly"), layEggs: () => console.log("egg") });

由于篇幅有限,关注小编,获取后续更多关于 typescript 使用技巧

如果觉得文章不错的话,帮忙点一个赞 👍 吧, 感谢


前端蛋卷
238 声望4 粉丝