首页 > 解决方案 > TypeScript 中的泛型函数:“T”可以用与“X”无关的任意类型实例化

问题描述

我有一些 TypeScript 代码,我试图通过为函数引入泛型类型来概括,但 TypeScript 给了我一个我无法完全理解的错误消息。

我已将我的代码缩减为以下示例代码,其中添加到示例中的注释有望显示我的意图:

/**
 * The types in this example
 */

// A "container" object, which has folders array, 
// which is of either FolderString or FolderNumber.
interface FolderContainer<T extends FolderString | FolderNumber> {
  folders: T[]
}

// Bear with me; the kind of two silly types, just for the sake of the example:
interface FolderString extends FolderContainer<FolderString> {
  age: string
}

interface FolderNumber extends FolderContainer<FolderNumber> {
  age: number
}

/**
 * Utility function for mapping each folder in the data structure,
 * but keep the structure (eg. you cannot map each folder to another type)
 * 
 * I wrote T as T extends {folders: T[]}, as I believe this is enough to describe
 * what the shape of T is in order for the fn to work.
 *
 * I did not think I should set T to for example 
 * T extends FolderString | FolderNumber as that is, as far as I can understand,
 * irrelevant for the mapFoldersKeepStructure fn.
 */
export function mapFoldersKeepStructure<T extends {folders: T[]}>(
  folders: T[],
  mapper: (folder: T, parents: readonly T[]) => T,
  parents: readonly T[] = []
): T[] {
  return folders.map((folder) => {
    const f = mapFoldersKeepStructure(folder.folders, mapper, [...parents, folder])
    return mapper({...folder, folders: f}, parents)
  })
}

/**
 * Two objects which has the shape of the FolderContainer, where it's folders is of the
 * type given
 */
const stringFolders: FolderContainer<FolderString> = {folders: [{age: '13', folders: []}]}
const numberFolders: FolderContainer<FolderNumber> = {folders: [{age: 13, folders: []}]}


// First example of mapFoldersKeepStructure, which works as I expect.
// mapReturn is FolderString[]
const mapReturn = mapFoldersKeepStructure(stringFolders.folders, (folder) => {
  return folder
})

// Second example of usage, now wrapped in someFunction()
function someFunction(folders: FolderNumber[]): FolderNumber[] {
  return mapFoldersKeepStructure(folders, (folder) => {
    return folder
  })
}

// Second example works and returns type as I expect.
// someFunctionReturn is FolderNumber[]
const someFunctionReturn = someFunction(numberFolders.folders)

/**
 * I want to make the previous someFunction() generic. 
 * I thought my first tiny step could be to substitute FolderNumber type with a generic type.
 *
 * I though this change would not do much special, but obviously this is where my 
 * current understanding of TS and generics starts to break down, as TS responds with:
 *
 *  Type '{ folders: T[]; }[]' is not assignable to type 'T[]'.
 *    Type '{ folders: T[]; }' is not assignable to type 'T'.
 *      'T' could be instantiated with an arbitrary type which could be unrelated to '{ folders: T[]; }'.ts(2322)
 */
function someFunctionGeneric<T extends FolderNumber>(folders: T[]): T[] {
  return mapFoldersKeepStructure(folders, (folder) => {
    return folder
  })
}

如果您能引导我了解或解释我缺少哪个难题来理解这一点,那将有很大帮助:-)

标签: typescriptgenericstypescript-generics

解决方案


推荐阅读