函数重载以下问题来自于与公司小伙伴以及网友的讨论,整理成章,希望提供另一种思路(避免踩坑)解决问题。
Typescript 提供函数重载的功能,用来处理因函数参数不同而返回类型不同的使用场景,使用时,只需为同一个函数定义多个类型即可,简单使用如下所示:
declare function test(a: number): number;declare function test(a: string): string;const resS = test('Hello World'); // resS 被推断出类型为 string;const resN = test(1234); // resN 被推断出类型为 number;它也适用于参数不同,返回值类型相同的场景,我们只需要知道在哪种函数类型定义下能使用哪些参数即可。
考虑如下例子:
interface User {
name: string;
age: number;
}declare function test(para: User | number, flag?: boolean): number;在这个 test 函数里,我们的本意可能是当传入参数 para 是 User 时,不传 flag,当传入 para 是 number 时,传入 flag。Typescript 并不知道这些,当你传入 para 为 User 时,flag 同样允许你传入:
const user = {
name: 'Jack',
age: 666}// 没有报错,但是与想法违背const res = test(user, false);使用函数重载能帮助我们实现:
interface User {
name: string;
age: number;
}declare function test(para: User): number;declare function test(para: number, flag: boolean): number;const user = {
name: 'Jack',
age: 666};// bingo// Error: 参数不匹配const res = test(user, false);实际项目中,你可能要多写几步,如在 class 中:
interface User {
name: string;
age: number;
}const user = {
name: 'Jack',
age: 123};class SomeClass {
public test(para: User): number;
public test(para: number, flag: boolean): number; public test(para: User | number, flag?: boolean): number { // 具体实现
return 11;
}
}const someClass = new SomeClass();// oksomeClass.test(user);
someClass.test(123, false);// ErrorsomeClass.test(123);
someClass.test(user, false);映射类型自从 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:
// Compile with --noImplicitThistype ObjectDescriptor= { data?: D; methods?: M & ThisType ; // Type of 'this' in methods is D & M}function makeObject (desc: ObjectDescriptor ): 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; // Strongly typed this this.y += dy; // Strongly typed this } } }); obj.x = 10; obj.y = 20; obj.moveBy(5, 5);
正是由于 ThisType 的出现,Vue 2.5 才得以增强对 Typescript 的支持。
虽已内置了很多映射类型,但在很多时候,我们需要根据自己的项目自定义映射类型:
比如你可能想取出接口类型中的函数类型:
type FunctionPropertyNames= { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];type FunctionProperties = Pick >; interface Part { id: number; name: string; subparts: Part[]; updatePart(newName: string): void; }type T40 = FunctionPropertyNames ; // "updatePart"type T42 = FunctionProperties ; // { updatePart(newName: string): void }
比如你可能为了便捷,把本属于某个属性下的方法,通过一些方式 alias 到其他地方。
举个例子:SomeClass 下有个属性 value = [1, 2, 3],你可能在 Decorators 给类添加了此种功能:在 SomeClass 里调用 this.find() 时,实际上是调用 this.value.find(),但是此时 Typescript 并不知道这些:
class SomeClass {
value = [1, 2, 3];
someMethod() { this.value.find(); // ok
this.find(); // Error:SomeClass 没有 find 方法。
}
}借助于映射类型,和 interface + class 的声明方式,可以实现我们的目的:
type ArrayMethodName = 'filter' | 'forEach' | 'find'; type SelectArrayMethod= { [K in ArrayMethodName]: Array [K] }interface SomeClass extends SelectArrayMethod {}class SomeClass { value = [1, 2, 3]; someMethod() { this.forEach() // ok this.find() // ok this.filter() // ok this.value // ok this.someMethod() // ok } }const someClass = new SomeClass(); someClass.forEach() // oksomeClass.find() // oksomeClass.filter() // oksomeClass.value // oksomeClass.someMethod() // ok
导出 SomeClass 类时,也能使用。
可能有点不足的地方,在这段代码里 interface SomeClass extends SelectArrayMethod
类型断言用来明确的告诉 Typescript 值的详细类型,合理使用能减少我们的工作量。
比如一个变量并没有初始值,但是我们知道它的类型信息(它可能是从后端返回)有什么办法既能正确推导类型信息,又能正常运行了?有一种网上的推荐方式是设置初始值,然后使用 typeof 拿到类型(可能会给其他地方用)。然而我可能比较懒,不喜欢设置初始值,这时候使用类型断言可以解决这类问题:
interface User {
name: string;
age: number;
}
export default class NewRoom extends Vue { private user = {} as User;
}在设置初始化时,添加断言,我们就无须添加初始值,编辑器也能正常的给予代码提示了。如果 user 属性很多,这样就能解决大量不必要的工作了,定义的 interface 也能给其他地方使用。
枚举类型枚举类型分为数字类型与字符串类型,其中数字类型的枚举可以当标志使用:
// https://github.com/Microsoft/Typescript/blob/master/src/compiler/types.ts#L3859export const enum ObjectFlags {
Class = 1 << 0, // Class
Interface = 1 << 1, // Interface
Reference = 1 << 2, // Generic type reference
Tuple = 1 << 3, // Synthesized generic tuple type
Anonymous = 1 << 4, // Anonymous
Mapped = 1 << 5, // Mapped
Instantiated = 1 << 6, // Instantiated anonymous or mapped type
ObjectLiteral = 1 << 7, // Originates in an object literal
EvolvingArray = 1 << 8, // Evolving array type
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
ContainsSpread = 1 << 10, // Object literal contains spread operation
ReverseMapped = 1 << 11, // Object contains a property from a reverse-mapped type
JsxAttributes = 1 << 12, // Jsx attributes type
MarkerType = 1 << 13, // Marker type used for variance probing
JSLiteral = 1 << 14, // Object type declared in JS - disables errors on read/write of nonexisting members
ClassOrInterface = Class | Interface
}在 Typescript src/compiler/types 源码里,定义了大量如上所示的基于数字类型的常量枚举。它们是一种有效存储和表示布尔值集合的方法。
在 《深入理解 Typescript》 中有一个使用例子:
enum AnimalFlags {
None = 0,
HasClaws = 1 << 0,
CanFly = 1 << 1,
HasClawsOrCanFly = HasClaws | CanFly
}
interface Animal { flags: AnimalFlags;
[key: string]: any;
}function printAnimalAbilities(animal: Animal) { var animalFlags = animal.flags; if (animalFlags & AnimalFlags.HasClaws) { console.log('animal has claws');
} if (animalFlags & AnimalFlags.CanFly) { console.log('animal can fly');
} if (animalFlags == AnimalFlags.None) { console.log('nothing');
}
}var animal = { flags: AnimalFlags.None };
printAnimalAbilities(animal); // nothinganimal.flags |= AnimalFlags.HasClaws;
printAnimalAbilities(animal); // animal has clawsanimal.flags &= ~AnimalFlags.HasClaws;
printAnimalAbilities(animal); // nothinganimal.flags |= AnimalFlags.HasClaws | AnimalFlags.CanFly;
printAnimalAbilities(animal); // animal has claws, animal can fly上例代码中 |= 用来添加一个标志,&= 和 ~ 用来删除标志,| 用来合并标志。
作者:三毛丶
链接:https://www.jianshu.com/p/1b7acebcde2f



