前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >泛型_TypeScript笔记6

泛型_TypeScript笔记6

作者头像
ayqy贾杰
发布于 2019-06-12 07:09:04
发布于 2019-06-12 07:09:04
1.1K00
代码可运行
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬
运行总次数:0
代码可运行

一.存在意义

考虑这样一个场景,identity函数接受一个参数,并原样返回:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function identity(arg) {
 return arg;
}

从类型上看,无论参数是什么类型,返回值的类型都与参数一致,借助重载机制,可以这样描述:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function identity(arg: number): number;
function identity(arg: string): string;
function identity(arg: boolean): boolean;
// ...等无数个 a => a 的类型描述

重载似乎并不能满足这个场景,因为我们没有办法穷举arg的所有可能类型。既然参数是任意类型,不妨用any试试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function identity(arg: any): any;

覆盖到了所有类型,却丢失了参数与返回值的类型对应关系(上面相当于A => B的类型映射,而我们想要描述的是A => A

泛型与any

那么,应该如何表达两个any之间的对应关系呢?

用泛型。这样描述:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function identity<T>(arg: T): T {
 return arg;
}

类型变量Tany类似,相当于具名any,这样就能明确表达T => T(即A => A)的类型映射了

二.类型变量

Type variable, a special kind of variable that works on types rather than values.

普通变量代表一个值,而类型变量代表一个类型

从作用上看,变量能够搬运值,而类型变量搬运的是类型信息:

This allows us to traffic that type information in one side of the function and out the other.

三.泛型函数

类型变量也叫类型参数,与函数参数类似,区别在于函数参数接受一个具体值,而类型参数接受一个具体类型,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function identity<T>(arg: T): T {
 return arg;
}// 传参给类型参数
// identity<number>
// 传参给函数参数(自动推断类型参数)
identity(1);
// 传参给函数参数(显式传入类型参数)
identity<number>(1);

带有类型参数的函数称为泛型函数,其中类型参数代表任意类型(any and all types),所以只有所有类型共有的特征才能访问:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function loggingIdentity<T>(arg: T): T {
 // 报错 Property 'length' does not exist on type 'T'.
 console.log(arg.length);
 return arg;
}

实际上,因为有void这个空集在,所以并不存在所有类型通用的属性或方法。也不能对类型变量做任何假设(比如假定它有length属性),因为它代表一个任意类型,没有任何约束

除此之外,类型变量T就像一个具体类型一样,可以用于任何具体类型出没的地方:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function loggingIdentity<T>(arg: T[]): T[] {
 console.log(arg.length);  // Array has a .length, so no more error
 return arg;
}
// 或者
function loggingIdentity<T>(arg: Array<T>): Array<T> {
 console.log(arg.length);  // Array has a .length, so no more error
 return arg;
}

类型描述

泛型函数的类型描述与普通函数类似:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 普通函数
let myIdentity: (arg: string) => string =
 function(arg: string): string {
   return arg;
 };
// 泛型函数
let myIdentity: <T>(arg: T) => T =
 function<T>(arg: T): T {
   return arg;
 };

仍然是箭头函数语法,只是在(参数列表)前增加了<类型参数列表>。同样的,类型描述中类型参数名也可以与实际的不一致:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let myIdentity: <U>(arg: U) => U =
 function<T>(arg: T): T {
   return arg;
 };

P.S.特殊的,函数类型描述还可以写成对象字面量的形式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 泛型函数
let myIdentity: { <T>(arg: T): T };
// 普通函数
let myIdentity: { (arg: string): string };

像是接口形式类型描述的退化版本,没有复用优势,也不如箭头函数简洁,因此,并不常见

四.泛型接口

带类型参数的接口叫泛型接口,例如可以用接口来描述一个泛型函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interface GenericIdentityFn {
 <T>(arg: T): T;
}

还有一种非常相像的形式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interface GenericIdentityFn<T> {
 (arg: T): T;
}

这两种都叫泛型接口,区别在于后者的类型参数T作用于整个接口,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interface GenericIdentity<T> {
 id(arg: T): T;
 idArray(...args: T[]): T[];
}
let id: GenericIdentity<string> = {
 id: (s: string) => s,
 // 报错 Types of parameters 's' and 'args' are incompatible.
 idArray: (...s: number[]) => s,
};

接口级的类型参数有这种约束作用,成员级的则没有(仅作用于该泛型成员)

五.泛型类

同样,带类型参数的类叫泛型类,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class GenericNumber<T> {
 zeroValue: T;
 add: (x: T, y: T) => T;
}

像接口一样,泛型类能够约束该类所有成员关注的目标类型一致:

Putting the type parameter on the class itself lets us make sure all of the properties of the class are working with the same type.

注意,类型参数仅适用于类中的实例成员,静态成员无法使用类型参数,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class GenericNumber<T> {
 // 报错 Static members cannot reference class type parameters.
 static zeroValue: T;
}

因为静态成员在类实例间共享,无法唯一确定类型参数的具体类型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let n1: GenericNumber<string>;
// 期望 n1.constructor.zeroValue 是 string
let n2: GenericNumber<number>;
// 期望 n1.constructor.zeroValue 是 number,出现矛盾

P.S.这一点与Java一致,具体见Static method in a generic class?

六.泛型约束

类型参数太“泛”(any and all)了,在一些场景下,可能想要加以约束,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interface Lengthwise {
 length: number;
}function loggingIdentity<T extends Lengthwise>(arg: T): T {
 console.log(arg.length);  // Now we know it has a .length property, so no more error
 return arg;
}

通过接口来描述对类型参数的约束T extends constraintInterface),比如上面要求类型参数T必须具有一个number类型的length属性`

另一个典型的场景是工厂方法,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 要求构造函数c必须返回同一类(或子类)的实例
function create<T>(c: {new(): T; }): T {
 return new c();
}

此外,还可以在泛型约束中使用类型参数,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function getProperty<T, K extends keyof T>(obj: T, key: K) {
 return obj[key];
}let x = { a: 1, b: 2, c: 3, d: 4 };getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

能够用一个类型参数的特征去约束另一个类型参数,相当强大

七.总结

之所以叫泛型,是因为能够作用于一系列类型,是在具体类型之上的一层抽象:

Generics are able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.

参考资料

  • Generics
  • 类型参数 | 类型_Haskell笔记3
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Android--利用DrawerLayout打造自定义侧滑效果
自定义侧滑效果.gif 上次说到自定义属性在系统控件上的应用,今天继续利用这个思想,基于DrawerLayout打造自己的侧滑效果 首先看下我们的布局文件 <?xml version="1.0" en
aruba
2020/07/02
8060
TagLayout自定义流式布局
这是一个继承ViewGourp来实现的自定义布局。他的核心只有一个,即当子View的宽度超出自身最大宽度时,自动换行。那么,我们先来看核心代码:
饮水思源为名
2019/11/21
7190
Android如何实现超级棒的沉浸式体验
做APP开发的过程中,有很多时候,我们需要实现沉浸式的体验。
老码小张
2018/10/25
3K1
AnimatedPathView实现自定义图片标签
老早用过小红书app,对于他们客户端笔记这块的设计非常喜欢,恰好去年在小红书的竞争对手公司,公司基于产品的考虑和产品的发展,也需要将app社交化,于是在社区分享这块多多少少参照了小红书的设计,这里面就有一个比较有意思的贴纸,标签等设计,这里用到了GpuImage的库,这个demo我也将代码开源了,有需要的去fork我的github的代码,今天要说的是详情页面的AnimatedPathView实现可以动起来的标签。(之前我们项目中由于时间问题,将这种效果用h5实现了,不过现在回React Native之后,发
xiangzhihong
2018/02/05
1K0
AnimatedPathView实现自定义图片标签
anndroid 模糊引导界面
先上两张图,后面补上代码 我们以前的写法是在需要显示模糊引导的地方,写一个布局,然后第一次使用的时候显示出来。但是这样做代码结构不清晰,所以我们有必要将这些View独立出来,写成一个自定义的View
xiangzhihong
2018/02/01
7440
anndroid 模糊引导界面
良好的知识储备_listview控件的用法
在上一篇文章里,我总结了一下自定义控件需要了解的基础知识:View的绘制流程——《自定义控件知识储备-View的绘制流程》。其中,在View的测量流程里,View的测量宽高是由父控件的MeasureSpec和View自身的LayoutParams共同决定的。MeasureSpec是什么,上一篇文章里已经说得很清楚了(啥,没看过?快去路克路克,(๑•̀ㅂ•́)و✧)。而LayoutParams呢?是时候在这里做个了断了。
全栈程序员站长
2022/11/08
2900
良好的知识储备_listview控件的用法
相关推荐
Android--利用DrawerLayout打造自定义侧滑效果
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档