- Published on
Day21: Learning TypeScript: Generics
- Authors

- Name
- irisjustdoit
- @irisjustdoit
Day21: Learning TypeScript: Generics
Speaking of the community being all abuzz about the "Squid Game," I'm only on the first episode and really want to quit to binge-watch (I'm so lazy).
Thank you to those who haven't watched "Squid Game" and accidentally stumbled upon my article (or have you already finished it?).
I didn't write it well, so consider it a note. If there are any mistakes, please feel free to leave comments. Thank you!
Generics refer to a feature that allows you to define functions, interfaces, or classes without specifying concrete types in advance, and instead specify the types when using them.
When Can You Use It?
When introducing array types earlier, besides using "type + brackets" to represent an array, you can also use generics to represent it. Let's review:
// "type + brackets"
const list1: number[] = [1, 2, 3];
// Array generics
const list2: Array<number> = [1, 2, 3];
In this example, the type of the parameter I passed in is a number array type. We can use Array<number> or number[], and the return type is also the same number array type. So there's no problem when passing in [1, 2, 3].
function identity(arg: Array<number>): Array<number> {
console.log(arg);
return arg;
}
identity([1, 2, 3]); // [1, 2, 3]
function identity2(arg: number[]): number[] {
console.log(arg);
return arg;
}
identity2([1, 2, 3]); // [1, 2, 3]
If we want it to handle not only number arrays but also string arrays, what should we do? >> We can use generics, not specifying concrete types in advance, and instead specify or let the compiler infer the types when using them.
How to Use Generics?
By adding <Type> after the function name, it indicates a dynamic type. The naming of <Type> can also be defined by yourself, such as <List>. However, <T> and <Type> are more common.
Then specify the arg parameter as Type[] type, and the function also returns Type[] type, which allows returning data of the specified type based on the input type. Of course, you can also just write Type, but in this example, both the input and output types are arrays, so writing Type[] is more rigorous, and if it's a string, just writing Type is fine.
function identity3<Type>(arg: Type[]): Type[] {
return arg;
}
let output1 = identity3([1, 2, 3]);
let output2 = identity3(["a", "b", "c"]);
console.log(output1); // [1, 2, 3]
console.log(output2); // ["a", "b", "c"]
When calling the function, you can pass the parameter type to the function, such as specifying the parameter type as string using <type> in the example below: identity3<string>(["a", "b", "c"]). Or let TypeScript automatically infer the type (type argument inference), which is the most common method. Of course, this is the preferred option for convenience XD.
let output3 = identity3<string>(["a", "b", "c"]); // Specify the generic return type `<type>`
let output4 = identity3(["a", "b", "c"]); // inference
console.log(output3); // [ 'a', 'b', 'c' ]
console.log(output4); // [ 'a', 'b', 'c' ]
Multiple Parameter Situations
You can also use <T, U> to define custom types and correspond to the parameter types.
function makeTuple<T, U>(tuple: [T, U]): [T, U] {
return [tuple[0], tuple[1]];
}
const tuple1 = makeTuple([1, "a"]);
console.log(tuple1); // [ 1, 'a' ]
Let's also try the arrow function syntax:
const makeTuple2 = <T, U>(tuple: [T, U]): [T, U] => {
return [tuple[0], tuple[1]];
}
const tuple2 = makeTuple2([1, "a"]);
console.log(tuple2); // [ 1, 'a' ]
⚠ Arrow functions can be used in .ts but not in .tsx, because in tsx, the TS compiler cannot clearly distinguish whether < and > refer to JSX or Generics Type. Therefore, when defining generics in a function, use the traditional Function Statement directly.
Generic Constraints 泛型约束
When using generic variables inside a function, since we do not know what type it is in advance, we cannot arbitrarily operate on its properties or methods. For example, in the following example, the generic T does not necessarily contain the property length, so it will throw an error during compilation.
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
//error: Property 'length' does not exist on type 'T'.
At this point, we can constrain the generic type. We use extends to constrain the generic T to conform to the shape of the Lengthwise interface, meaning it must contain the length property. In the example below, if the type we pass in does not conform to the Lengthwise type, it will throw an error.
interface Lengthwise {
length: number;
}
function loggingIdentity2<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity2(3); //error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
loggingIdentity2({ length: 10, value: 3 });

Using Type Parameters in Generic Constraints
You can specify a type parameter constrained by another type. For example, the key parameter can only be of the type defined by the obj parameter.
In the example below, getProperty(x, "m") will throw an error indicating that there is no m parameter.
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
let value1 = getProperty(x, "a");
let value2 = getProperty(x, "m"); //error: Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
console.log(value1); //1
console.log(value2); //undefined
Generic Interface
含有泛型的介面來定義函式的形狀:
nterface GenericIdentityFn {
<Type>(arg: Type): Type;
}
function identityFn<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn = identityFn;
let result = myIdentity([1, 2, 3]);
console.log(result); //[ 1, 2, 3 ]
可以把泛型引數提前到介面名上:
interface GenericIdentityFn2<Type> {
(arg: Type): Type;
}
function identityFn2<Type>(arg: Type): Type {
return arg;
}
let myIdentity2: GenericIdentityFn2<number> = identityFn2;
let result2 = myIdentity2(123);
console.log(result2); //123
⚠ 在使用泛型介面的時候,需要定義泛型的型別。
Generic Classes
泛型也可以用於類別的型別定義中:
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
//限制為 number 型別
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 123)); //123
//限制為 string 型別
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};
console.log(stringNumeric.add(stringNumeric.zeroValue, "test")); //test
感謝閱讀, 明天見!
參考資料
https://willh.gitbook.io/typescript-tutorial/advanced/generics https://www.typescriptlang.org/docs/handbook/2/generics.html https://pjchender.dev/typescript/ts-generics#%E5%9F%BA%E6%9C%AC%E8%AA%9E%E6%B3%95
