本篇是对泛型的初步认识与应用。
之前看到过一张图,用来形容ts的学习曲线哈哈。
0 初识泛型
使用泛型最能解决的问题是,期待输入未知类型的变量,输出时也是同样的类型。
有如下场景:
function echo (arg:any) :any{
return arg;
}
const rst:string = echo(123);
// 因函数返回值为any,string也属于any故而不会报错
这就导致了对输入输出值依然未知,甚至引发容易忽视的bug,接下来将使用泛型进行改造。
function echo<T> (arg:T) :T{
return arg;
}
const rst = echo('str');
通过<>的形式传入泛型T(可以是任何字母,习惯用T),用法类似于ts内置的接口与泛型,例如number[]可以写为Arrary
使用泛型也可传入多种类型,例如传入一个包含两种数据类型的元组,返回交换类型顺序后的元组。
function swap<T,U> (tuple:[T,U]) : [U, T]{
return [tuple[1], tuple[0]];
}
const rst = swap('string',123)
// 此时rst的类型推断为[number,string]
1 约束泛型
使用泛型时,由于可传入任何类型的值,所以有时可能会产生错误,例如:
function echoWitfhArr<T> (arg:T) :T {
consle.log(arg.length)
return arg;
}
此时会产生错误,因为arg不一定包含length属性,所以需对泛型进行一些简单的约束,可将上一个函数改造为:
function echoWitfhArr<T> (arg:T[]) :T {
consle.log(arg.length)
return arg;
}
此时arg为泛型数组,拥有length属性。但如果不想将输入值约定为数组,还可使用interface进行更将灵活的约束。
interface IWithLength {
length: number;
}
function echoWithLength<T extends IWithLength>(arg:<T>):T {
consle.log(arg.length)
return arg;
}
const str = echoWithLength('123');
const obj = echoWithLength({length:5, width:'3'});
const arr = echoWithLength([1,2,3]);
const err = echoWithLength(12); // 报错
以上示例说明只要包含length属性,不管是string,number还是array都可以。
这也是程序设计中,动态类型的一种风格(Duck Typing)即:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
2 泛型在接口和类中的使用
在真实使用场景还会遇到这样的情况:创建一个队列类Queue,根据先进先出规则定义push和pup方法——
class Queue {
private data = [];
push(item) {
return this.data.push(item);
}
pop() {
return this.data.shift();
}
}
const queue = new Queue();
queue.push(1)
queue.push('str')
console.log(queue.pop().toFixed());
console.log(queue.pop().toFixed());
此时在编辑器中不会报错,但如果使用tsc编译js文件并运行,则会报错:queue.pop().toFixed() is not a function.
原因是,我们可以向队列中加入进任意类型的值,当然弹出时也会是任意类型,但由于调用了只有number才拥有的方法toFixed(),所以报了一个编辑器都无法捕捉的错误。
一种快速的解决方法是将push的值定义为number类型:
push(item:number) {
return this.data.push(item);
}
但快速也意味着痛苦,如果需要传入其他类型的值,将不得不反复修改。我们真正的需求是,不管传入什么类型的值,都返回相同的类型,故此时可以使用泛型来进行约束。
class Queue<T> {
private data = [];
push(item :T) {
return this.data.push(item);
}
pop(): T {
return this.data.shift();
}
}
const queue = new Queue<number>();
queue.push(1);
queue.push('str'); // 报错 无法再传入string类型数据了
console.log(queue.pop().toFixed());
console.log(queue.pop().toFixed());
同样的,泛型也可用于interface定义:
interface IKeyPair <T , U> {
key: T
value: U
}
let ap1: IKeyPair<number, string> = {key: 1, value: 'string'};
let ap2: IKeyPair<string, number> = {key: 'str' ,value: 2};
以上就是对泛型的初步认识,到此也可以更好的理解ts内置接口与泛型的应用Array
如有疑问,欢迎与我讨论👏