MST

星途 面试题库

面试题:TypeScript 上下文类型推导对泛型约束及逆变协变的影响

在 TypeScript 中,定义一个泛型函数 `transform<T, U>(arr: T[], mapper: (arg: T) => U): U[]`,用于对数组进行映射转换。假设 `T` 是一个具有逆变性质的类型,`U` 是一个具有协变性质的类型。分析在不同的上下文环境下,上下文类型推导是如何影响这个泛型函数中 `T` 和 `U` 的类型推导,以及如何在复杂类型系统中确保类型安全和正确的类型推导。请结合实际代码示例进行详细阐述。
31.9万 热度难度
前端开发TypeScript

知识考点

AI 面试

面试题答案

一键面试

1. 泛型函数定义

function transform<T, U>(arr: T[], mapper: (arg: T) => U): U[] {
    return arr.map(mapper);
}

2. 上下文类型推导对 TU 的影响

逆变性质的 T

  • 逆变意味着在类型赋值中,如果 ST 的子类型,那么 (T) => any(S) => any 的子类型。
  • 示例:
// 定义两个类型
type Animal = { name: string };
type Dog = { name: string; breed: string };

// 定义一个逆变函数
function feedAnimal(animal: Animal) {
    console.log(`Feeding ${animal.name}`);
}

// 可以将 feedAnimal 赋值给 feedDog,因为 Dog 是 Animal 的子类型,而函数参数类型是逆变的
function feedDog(dog: Dog) {
    console.log(`Feeding ${dog.name}, a ${dog.breed}`);
}
let feedFunction: (dog: Dog) => void = feedAnimal;

// 在 transform 函数中,如果传入的 mapper 函数参数类型更具体,T 会推导为更具体的类型
let animals: Animal[] = [{ name: 'Tom' }];
let dogMapper = (dog: Dog) => dog.breed;
// 这里 T 会推导为 Dog,因为 dogMapper 的参数类型是 Dog
let dogBreeds = transform(animals, dogMapper); 

协变性质的 U

  • 协变意味着在类型赋值中,如果 ST 的子类型,那么 () => S() => T 的子类型。
  • 示例:
// 定义两个类型
type Pet = { name: string };
type Dog = { name: string; breed: string };

// 定义一个协变函数
function getPet(): Pet {
    return { name: 'Buddy' };
}

// 可以将 getPet 赋值给 getDog,因为 Dog 是 Pet 的子类型,而函数返回值类型是协变的
function getDog(): Dog {
    return { name: 'Max', breed: 'Golden Retriever' };
}
let getFunction: () => Pet = getDog;

// 在 transform 函数中,如果 mapper 函数返回值类型更具体,U 会推导为更具体的类型
let dogs: Dog[] = [{ name: 'Max', breed: 'Golden Retriever' }];
let petMapper = (dog: Dog) => ({ name: dog.name });
// 这里 U 会推导为 Pet,因为 petMapper 的返回值类型是 Pet
let pets = transform(dogs, petMapper); 

3. 在复杂类型系统中确保类型安全和正确的类型推导

  • 明确类型标注:在复杂类型系统中,为了确保类型安全和正确的类型推导,可以对泛型参数进行明确的类型标注。
// 明确标注 T 和 U 的类型
let numbers: number[] = [1, 2, 3];
let stringMapper = (num: number) => num.toString();
let strings = transform<number, string>(numbers, stringMapper); 
  • 使用类型保护和断言:在 mapper 函数内部,如果涉及到复杂的类型判断和转换,可以使用类型保护和断言。
type Shape = { kind: string };
type Circle = { kind: 'circle'; radius: number };
type Square = { kind:'square'; side: number };

function area(shape: Shape): number {
    if (shape.kind === 'circle') {
        let circle = shape as Circle;
        return Math.PI * circle.radius * circle.radius;
    } else {
        let square = shape as Square;
        return square.side * square.side;
    }
}

let shapes: Shape[] = [{ kind: 'circle', radius: 5 }, { kind:'square', side: 4 }];
let areas = transform(shapes, area); 
  • 利用工具类型:TypeScript 提供了很多工具类型,如 ExcludeExtractNonNullable 等,可以帮助在复杂类型系统中进行类型推导和类型安全检查。
type AllNumbers = number | string;
type OnlyNumbers = Exclude<AllNumbers, string>;

function filterNumbers(arr: AllNumbers[]): OnlyNumbers[] {
    return arr.filter((value): value is OnlyNumbers => typeof value === 'number') as OnlyNumbers[];
}

let mixedArray: AllNumbers[] = [1, 'two', 3];
let numbersOnly = transform(mixedArray, filterNumbers);