1. 泛型有什么用?
// 考虑以下场景
// 函数名为add,可以用于数字、字符串等相加减
// [Func - 数字类型]
function printLiteral(arg: number) {
console.log(arg);
}
// [Func - 字符串类型]
function printLiteral(arg: string) {
console.log(arg);
}
上述代码明显有重复,违背DRY原则,难道只能写成如下形式么?
// [Func - 任意类型]
function printLiteral(arg: any) {
console.log(arg);
}
这样写其实失去了使用类型标记的意义,全部标记为any == 没有标记类型
这时候就要祭出泛型了
function printLiteral<T>(arg: T) {
console.log(arg);
}
@思考1-1 能否约束T类型让其是number或者string中的一种?
@思考1-2 能否让参数数量可变,类似console.log?
|
2. keyof关键字
如何要实现一个取泛型中的属性的函数?
// [Func - Unknow]
function pickValue<T>(obj: T, prop: string) {
return obj[prop];
}
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'
// [Func - object]
function pickValue<T extends object>(obj: T, prop: string) {
return obj[prop];
}
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'
// [Func - Trick]
function pickValue<T extends {[prop: string]: any}>(obj: T, prop: string) {
return obj[prop];
}
// 不报错了,但是 == 写了个any,没有任何约束
关键字keyof能够获取类型中的包含字段
function pickValue<T extends object, U extends keyof T>(obj: T, prop: U) {
return obj[prop];
}
@思考2-1 能否约束T使其能够支持new操作
function createInstance<T>(type: T): T {
return new type();
}
|
3. 实现Partial
// 假设有以下类型
type Person = {
name: string,
age: number,
temperature: number,
}
// 其所有属性要改成可选的
type OptionPerson = {
name?: string,
age?: number,
temperature?: number,
}
// 有什么办法可以做到么?
有,内置工具Partial已经实现了
type OptionPerson = Partial<Person>
能否自己实现一下Partial?
type MPartial<T> = {
[prop in keyof T]?: T[prop] | undefined;
}
@思考3-1 能否实现一个Partial将属性的可选值改为必填值?
|
4. 实现Pick
实现一个函数的pickValue,借鉴2中keyof关键字的介绍,可以实现如下
function pickValues<T extends object, U extends keyof T>(obj: T, props: U[]) {
return props.map(prop => obj[prop]);
}
如果是通过Pick新建一个类型呢
type Person = {
name: string,
age: number,
temperature: number,
}
type HealthyPerson = Pick<Person, "name"|"temperature">
// type HealthyPerson = {
// name: string;
// temperature: number;
// }
@思考4-1 如何实现一个内置的Pick工具?
|
5. 条件类型 - 实现Exclude
条件类型
declare function getValue<T extends boolean>(arg: T): T extends true ? string : number;
getValue(true); // function getValue<true>(arg: true): string
getValue(false); // function getValue<false>(arg: false): number
Exclude内置工具效果
type NumberOrString = Exclude<number| string| undefined, undefined>;
@思考5-1 如何实现一个内置的Exclude工具?
@思考5-2 如何提取接口中的函数名称?
type Person = {
name: string,
age: number,
temperature: number,
fromWhere: () => string,
measureTemperature: () => number,
}
type funNames = extractFunctionName<Person>; // "fromWhere" | "measureTemperature"
|
6. 实现Omit
工具效果
type Person = {
name: string,
age: number,
temperature: number,
}
type UsefulInfo = Omit<Person, "age">;
// type UsefulInfo = {
// name: string;
// temperature: number;
// }
@思考6-1 如何实现一个内置Omit工具?
|
7. infer关键字 - 实现ReturnType
infer效果
type paramType<T extends (arg: any) => any> = T extends ((arg: infer U) => any) ? U : never;
type testFunc = (a: boolean) => number;
type testType = paramType<testFunc>; // boolean
@思考7-1 如何实现一个ReturnType工具?
|
8. 实现Record
type WaterBands = "NongFuSpring" | "Cestbon" | "Ganten";
const bottledWaterList = {
"NongFuSpring": { net: 550 },
"Cestbon": { net: 555 },
"Ganten": { net: 560 },
}
风险点: 错写漏写bottledWaterList中的WaterBands品牌都不会报错,其属性net拼错也不会报错
此时需要借助Record实现bottledWaterList的类型定义
type WaterList = Record<WaterBands, { net: number }>
// type WaterList = {
// NongFuSpring: {
// net: number;
// };
// Cestbon: {
// net: number;
// };
// Ganten: {
// net: number;
// };
// }
@思考8-1 如何实现一个Record工具?
|
9. 实战ts笔试题
|
参考答案
思考1-1
function printLiteral<T extends string|number>(arg: T) {
console.log(arg);
}
// 限制类型为string或number
printLiteral(true) // 类型“true”的参数不能赋给类型“string | number”的参数
思考1-2
function printLiteral<T extends string|number>(...args: T[]) {
console.log(...args);
}
思考2-1
function createInstance<T extends {new(): T}>(type: T): T {
return new type();
}
思考3-1
type RPartial<T> = {
[prop in keyof T]-?: T[prop]
}
思考4-1
type MPick<T, U extends keyof T> = {
[k in U]: T[k]
}
思考5-1
type MExclude<T, U> = T extends U ? never : T;
思考5-2
type extractFunctionName<T> = {
[k in keyof T]: T[k] extends Function ? k : never;
}[keyof T];
思考6-1
type MOmit<T, U extends keyof T> = MPick<T, MExclude<keyof T, U>>
思考7-1
type MReturnType<T extends (...arg: any) => any> = T extends (...arg: any) => infer U ? U : any;
思考8-1
type MRecord<T extends keyof string|number|symbol, U> = {
[k in T]: U
}