# 泛型类
当你使用泛型类时候在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型
interface ParameterlessConstructor<T = any> {
new(): { hi: T }; //这里其实是做映射,只取出hi对应的值
}
class ExampleOne {
hi() {
alert('Hi');
}
test() { }
}
class Creator<T> {
private ctor: ParameterlessConstructor<T>
constructor(_ctor: ParameterlessConstructor<T>) {
this.ctor = _ctor;
}
// 也可以直接这样写
constructor(private ctor: ParameterlessConstructor<T>) {}
getNew() {
return new this.ctor();
}
}
var creator = new Creator(ExampleOne);
var example = creator.getNew();
example.hi(); //这里只能取到hi方法,不能取到test
# 函数重载
函数重载也可以使用联合类型取代他
// 参数的名字都无所为关键个数得一一对应
export function test(type: string, children: string): string
export function test(
type: number,
props: number
): string
export function test(
type: string | number,
propsOrChildren: string | number, //这个参数对应上面的定义,他至少得包含上面的所有类型
children?: string | number
): string {
return ' ste'
}
# 泛型约束
相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就 是说至少包含这一属性;为此,我们需要列出对于T的约束要求,我们定义一个接口来描述约束条件,创建一个包含.length属性的接口 ,使用这个接口和extends关键字来实现约束:
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;
}
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
# Record
ts文档上对Record的介绍不多,但却经常用到,Record是一个很好用的工具类型
// 先看下record源码
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// K可以是联合类型、对象、枚举
// 即将K中的每个属性([P in K]),都转为T类型
type proxyKType = Record<K,T>
type petsGroup = 23 | 'cat' | 'fish';
interface IPetInfo {
name: string,
age: number,
}
type IPets = Record<petsGroup, IPetInfo>;
const animalsInfo: IPets = {
23: {
name: 'dogName',
age: 2
},
cat: {
name: 'catName',
age: 3
},
fish: {
name: 'fishName',
age: 5
}
}
// Record<any, any>就是将每一个x中的每一个属性转换为任何类型,这里x也可以是K可以是联合类型、对象、枚举
let myAdd = (x: any): x is Record<any, any> => x;
# 类型推论
这叫做“按上下文归类”,是类型推论的一种,尝试这个例子的时候,你会发现如果你在赋值语句的一边指定了类型但是另一边没有类型 的话,TypeScript编译器会自动识别出类型:
// myAdd has the full function type
let myAdd = function (x: number, y: number): number { return x + y; };
// The parameters `x` and `y` have the type number
let myAdd: (baseValue: number, increment: number) => number =
function (x, y) { return x + y; };
type Slot = (...args: any[]) => boolean;
const normalizeSlot = (key: number, rawSlot: Function): Slot => (
props: any
) => {
return false;
}
const a = normalizeSlot(333, function () { }); // 根据定义a方法只要理解我接受多个任
// 意参数且返回布尔值的函数就行
alert(a()); //false
# interface的extend
export interface VNode<HostNode = any, HostElement = any> {
_isVNode: true
props: Record<any, any> | null
anchor: HostNode | null
target: HostElement | null
}
// 根据初始化传入的anchor值的属性来确定HostNode这个是啥类型
type VNodeChildAtom<HostNode, HostElement> =
| VNode<HostNode, HostElement>
| string
| number
| boolean
| null
| void
// VNodeChildren接口主要继承VNodeChildAtom类的数组
export interface VNodeChildren<HostNode = any, HostElement = any>
extends Array<
VNodeChildAtom<HostNode, HostElement>
> { }
function printLabel(labelledObj: VNodeChildren) {
console.log(labelledObj);
}
printLabel([{ _isVNode: true, props: {}, anchor: 23, target: 'adsfasd' }]);
printLabel([23, 54, 'fasdf']);
// 通过或操作也可以循环创建多维数组vnode
export interface VNodeChildren<HostNode = any, HostElement = any>
extends Array<
| VNodeChildren<HostNode, HostElement> | VNodeChildAtom<HostNode, HostElement>
> { }
printLabel([[{ _isVNode: true, props: {}, anchor: 23, target: 'adsfasd' }], []]);
接口之间相互继承,属性名可以重复,但是类型必须一致,泛型内部的extends是用来做泛型约束的
interface A {
name: string
}
interface B {
age: number
}
// 接口可以继承多个其他接口B可以有与A属性名一样的属性名,但类型必须相同,否则C同时继承A、B会报错
interface C extends A, B {
sex: number
}
let c1: C = {
name: 'abc',
sex: 0,
age: 10
}
# Readonly
// 源码实现
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age?: number;
}
type person4 = Readonly<Person>;
// person4 === {
// readonly name: string;
// readonly age?: number;
// }
# 泛型接口
export interface SetupContext {
attrs: string
}
export type ComponentPropsOptions<P> =
| string[]
export interface FunctionalComponent<P = {}> {
(props: P, ctx: SetupContext): number
props?: ComponentPropsOptions<P>
displayName?: string
}
const test1: FunctionalComponent<number> = function (value, valu) {
return 3
};
test1(23, { attrs: "dfd" });
test1.props = [];
const test2: FunctionalComponent = function (value, value2) {
return 3;
}
test2(34, { attrs: "dfd" });
interface ConfigFn<T = number> { // 默认number类型
(value: T): T;
}
function getData<T>(value: T): T {
return value;
}
var myGetData: ConfigFn<object> = getData;
// 不写就用原来默认的number类型
var myGetData2: ConfigFn = getData;
// 传入新的默认类型
myGetData({});
myGetData2(23);
interface ConfigFnTwo {
<T>(value: T): T;
}
var testGetData: ConfigFnTwo = function <T>(value: T): T {
return value;
};
// 这种就是在调用时候传入类型,也不以不传
testGetData<string>('fdf');
# 联合类型
// 表示一个值可以是几种类型之一。 我们用竖线( |)分隔每个类型,所以number | string | boolean
// 如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
# 交叉类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性, 例如, Person & Serializable & Loggable同时是Person和Serializable和Loggable。 就是说这个类型的对象同时拥有 了这三种类型的成员; 我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在JavaScript 里发生这种情况的场合很多!) 下面是如何创建混入的一个简单例子
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();
# 泛型变量
function getData<T>(value:T):T{
return value;
}
getData<number>(123);
getData<string>('1214231');
//返回一个函数的写法
function injectHook() {
return 23;
}
const createHook = <T extends Function = () => any>(
lifecycle: string
) => (hook: T) => injectHook()
const onBeforeMount = createHook('fadfa')
onBeforeMount(function () { })
# ThisType
自从 TypeScript 2.1 版本推出映射类型以来,它便不断被完善与增强。在 2.1 版本中,可以通过 keyof 拿到对象 key 类型, 内置 Partial、Readonly、Record、Pick 映射类型;2.3 版本增加ThisType;2.8 版本增加 Exclude、Extract、 NonNullable、ReturnType、InstanceType;同时在此版本中增加条件类型与增强 keyof 的能力;3.1 版本支持对元组与数组 的映射。这些无不意味着映射类型在 TypeScript有着举足轻重的地位。其中 ThisType 并没有出现在官方文档中,它主要用来在对 象字面量中键入 this
type ObjectDescriptor<D, M> = {
data?: D;
methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
}
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
let data: object = desc.data || {};
let methods: object = desc.methods || {};
return { ...data, ...methods } as D & M;
}
let obj = makeObject({
data: { x: 0, y: 0 },
methods: {
moveBy(dx: number, dy: number) {
this.x += dx; // 这里就可以反问到data里面的值了
this.y += dy; // Strongly typed this
}
}
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);
alert(obj.x); //15
ts中函数使用this得先声明以及bind方法使用
const target = {
name: 4,
}
function instanceWatch(
this: object,
source: string | Function,
): string {
console.log(this);
console.log(source);
return "fasd";
}
const test = instanceWatch.bind(target);
test('fadsf');
# infer
infer 最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量
type ParamType<T> = T extends (param: infer P) => any ? P : T;
在这个条件语句T extends (param: infer P) => any ? P : T中,infer P 表示待推断的函数参数,整句表示为:如果
T能赋值给(param: infer P) => any,则结果是(param: infer P) => any类型中的参数 P,否则返回为 T
interface User {
name: string;
age: number;
}
type Func = (user: User) => void
type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string
上次我写了20多行,就为了获取一堆各种不同类型的数组里的元素类型,然而如果使用infer,会变得十分简单
type Elemenof<T> = T extends Array<infer E> ? E : T;
type Tuple = string[];
type TupleToUnion = Elemenof<Tuple>
// 如果泛型T是()=> infer R的子集,则返回infer R获取到的类型,否则返回boolean
type Func<T> = T extends () => infer R ? R : boolean;
let func1: Func<number>; // boolean;
let func2: Func<''>; // boolean
let func3: Func<() => Promise<number>>; // Promise<number>
type Obj<T> = T extends { a: infer VT, b: infer VT } ? VT : number;
let obj1: Obj<string>; // number;
let obj2: Obj<true>; // number;
let obj3: Obj<{a: string, b: string}>; // string
let obj4: Obj<a: number, b: string>; // string | number
// 当a、b为不同类型时,返回联合类型
# Partial、Pick
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface User {
id: number;
age: number;
name: string;
};
type PartialUser = Partial<User>
// 相当于: type PartialUser = { id?: number; age?: number; name?: string; }
type PickUser = Pick<User, "id" | "age">
// 相当于: type PickUser = { id: number; age: number; }
# keyof
interface Point {
x: number;
y: number;
}
type keys = "x" | "y"
type keys = keyof Point;
// 假设有一个 object 如下所示,我们需要使用 typescript 实现一个 get 函数来获取它的属性值:
const data = {
a: 3,
hello: 'world'
}
function get(o: object, name: string) {
return o[name]
}
// 我们刚开始可能会这么写,不过它有很多缺点:
// 无法确认返回类型:这将损失 ts 最大的类型校验功能
// 无法对 key 做约束:可能会犯拼写错误的问题
// 这时可以使用 keyof 来加强 get 函数的类型功能,有兴趣的同学可以看看 _.get 的 type 标记以及实现
function get<T extends object, K extends keyof T>(o: T, name: K): T[K] {
return o[name]
}
# 自定义映射
虽已内置了很多映射类型,但在很多时候,我们需要根据自己的项目自定义映射类型,比如你可能想取出接口类型中的函数类型
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
interface Part {
id: number;
name: string;
subparts: Part[];
updatePart(newName: string): void;
}
type test = FunctionPropertyNames<string>; // 这里必须要传值,除非上面定义时候设置了T = any有个默认值
type T40 = FunctionPropertyNames<Part>; // "updatePart"
type T42 = FunctionProperties<Part>; // { updatePart(newName: string): void }
const a: T40 = "updatePart";
const b: T42 = {
updatePart: function () { }
};
type MapSources<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}
type tttt = MapSources<string> // 那么tttt就是string类型
type test = MapSources<Part>
// 这里test实际上就是:
// 此时也无法给test类型赋值了,因为没有类型可以转换为never类型, 如果加了[keyof T]就同最上面一样了
type test = {
id: never;
name: never;
subparts: never;
updatePart: "updatePart";
}
# Omit
// 源码大概类似type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
interface User {
id: number;
age: number;
name: string;
};
// 相当于: type PickUser = { age: number; name: string; }
type OmitUser = Omit<User, "id">
# 类型别名与接口的异同
类型别名与接口有些类似,都支持类型参数,且可以引用自身,例如
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
interface ITree<T> {
value: T;
left: ITree<T>;
right: ITree<T>;
}
但存在一些本质差异:
- 类型别名并不会创建新类型,而接口会定义一个新类型
- 允许给任意类型起别名,但无法给任意类型定义与之等价的接口(比如基础类型)
- 无法继承或实现类型别名(也不能扩展或实现其它类型),但接口可以
- 类型别名能将多个类型组合成一个具名类型,而接口无法描述这种组合(交叉、联合等)
- 类型组合,接口无法表达这种类型
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
function findSomeone(people: LinkedList<Person>, name: string) {
people.name;
people.next.name;
people.next.next.name;
people.next.next.next.name;
}
// 应用场景上,二者区别如下:
// 接口:OOP场景(因为能被继承和实现,维持着类型层级关系)
// 类型别名:追求可读性的场景、接口无法描述的场景(基础类型、交叉类型、联合类型等)
← JavaScript CSS →