Home > Enterprise >  When should I use generic parameters in TypeScript?
When should I use generic parameters in TypeScript?

Time:08-27

I got this Typescript example from this tutorial

interface Logable {
  log: () => void;
}
function logItems<T extends Logable>(items: T[]): void {
  items.forEach(item => item.log());
}

And I'd like to know if, in this case, there's any difference between using the generic constraint or just using logItems(items: Logable[])

It seems like, if you don't need a more complex constraint, it's simpler to just specify the type of the parameter.

Am I right?

CodePudding user response:

You are right in thinking that it does not really make a difference. In a broader sense, as long as you are not using generics to model relations between parameters or for the return type, you might not need the generic type in the first place.

But there is a subtle difference. If we use the generic function, it is perfectly fine to pass an array of objects of any shape as long as the constraint is fulfilled.

function logItems<T extends Logable>(items: T[]): void {
  items.forEach(item => item.log());
}

logItems([
  { log: () => {} },
  { a: "123", log: () => {} }
])

Extra properties are allowed in this case.

Something different happens when we don't use a generic type.

function logItems2(items: Logable[]): void {}

logItems2([
  { log: () => {} },
  { a: "123", log: () => {} }
//  ^^^^^^^^ Error: Object literal may only specify known properties, and 'a' does not exist in type 'Logable'
])

Since there are extra checks for excess properties when you pass an object literal to the function, there is a compiler error.

But you could still pass the array as a variable to the function.

const arr = [
  { log: () => {} },
  { a: "123", log: () => {} }
]

logItems2(arr)

Playground

CodePudding user response:

When the the arguments or the return type depends on a type.

And in the case of that example, you are right, the generic parameter is not required.


A better example:

function logKey<T>(obj: T, key: keyof T) {
    console.log(obj[key])
}

logKey({ abc: 123 }, 'abc')

Here you cannot know what keys are allowed until you supply the object with some keys. In this case the type of the second argument depends on the type of the first.


Or:

function getFoo<T extends { foo: unknown }>(obj: T): T['foo'] {
    return obj.foo 
}

getFoo({ foo: 'a string' }) // string
getFoo({ foo: 123 }) // number

Here the return is different depending on the argument you supplied. In this case the return type depends on the type of the first argument.


If no type depends on any other type in your function, then your function does not need to be generic.

  • Related