I have some TypeScript code I'm trying to generalise by introducing generic types for the functions, but TypeScript gives me an error message I'm not able to fully understand.
I have reduced my code down to the following example code, where the comments added to the example hopefully shows my intentions:
/**
* 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
})
}
If you could lead me to, or explain, what piece of the puzzle I'm missing to understand this it would be of great help :-)