持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 5 天,点击查看活动详情
背景
在实际开发中,TS 应用的好不好,泛型的使用就能体现出来,到底是一个 ts 初级还是一个 ts 高阶呢?还有面试官考察 ts,基本也是出泛型相关题目。其重要性不言而喻了。
泛型学习资料其实很多,但是实际应用场景呢,实战中怎么把它用出来,用好呢,又是一个问题,接下来我们就统一整理一下实战场景应用,如有不全,请小伙伴们在留言区补充。
奥,之前有总结 TS 整体的笔记,可查看:TypeScript 入门修炼至出关,TS 项目中的使用# 再学Typescript,只为更好的运用它
泛型的定义
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
泛型就像一个占位符一个变量,在使用的时候我们可以将定义好的类型像参数一样传入,原封不动的输出。
设计泛型的关键目的是在成员之间提供有意义的约束, 这些成员可以是:
接口(interface)
别名 (type)
类的实例成员
类的方法
函数参数
函数返回值
泛型自己的约束
实战宝典
接口的约束
interface IInfo<T> {
nickname: T;
address: T;
}
const T13: IInfo<string> = {
nickname: "Nate",
address: 23423 // err
};
interface IInfoN<T> {
nickname: T;
address: string;
}
const KK: IInfoN<number | string> = {
nickname: 234,
address: "asdf"
};
KK.nickname = "";
定义函数的形状
interface ICreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: ICreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
};
限制函数参数及返回值类型
function test<T>(name: T): T {
return name;
}
let str: string;
str = test<number>(234); // err
// 泛型的自动推断
str = test(23); // err
限制构造函数参数类型以及类成员变量、方法参数的类型
class Animal<T> {
name: T;
constructor(name: T) {
this.name = name;
}
action(say: T): T {
return say;
}
}
let dog = new Animal<string>("旺财");
dog.action("23432");
定义多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap<number, string>([7, "seven"]); // ['seven', 7]
参数的默认类型
当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(23, {});
createArray<number>(23, 23);
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:
function loggingIdentity<T>(arg: T): T {
if (arg.length) {
console.log("arg");
}
return arg;
}
loggingIdentity("ssss");
我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:
interface ILengthWise {
length: number;
}
function loggingIdentity<T extends ILengthWise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity([]);
多个类型参数之间也可以互相约束
我们使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4, e: 20 };
let x2 = { b: 10, e: 20 };
copyFields(x, x2);
泛型接应用
// 改造前
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, "x"); // ['x', 'x', 'x']
// 改造后
function createArray<T>(length: number, value: T): Array<T> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<number>(3, 23); // ['x', 'x', 'x']
Promise 返回值的约束
function readFileAsync(path: string): Promise<string> {
return new Promise(resolve => {
resolve("234");
});
}
function cloneAsync<T>(
originObject: T,
callback?: (err: string, res: T) => void
): Promise<T> {
const data = {};
return new Promise(resolve => {
resolve(originObject);
});
}
cloneAsync<{ code: string; data: object; msg: string }>(234, () => {}).then(
res => {
console.log(res.code, res.data, res.msg);
}
);
// 不加类型
cloneAsync({ code: 12 }, () => {}).then(res => {
console.log(res.code);
});
接口函数的应用
import Axios, { AxiosRequestConfig } from "axios";
const fetch = <T>(config: AxiosRequestConfig): Promise<T> => {
return Axios(config).then(res => {
return res.data;
});
};
export function POST<T = any>(config: AxiosRequestConfig) {
config.method = "POST";
return fetch<T>(config);
}
interface IMatch {
matchTypes: string[];
}
function getMatchType(nid: number) {
return POST<HResponseData<{ matchTypes: string[] }>>({
url: "/common/plugin/match-type",
data: { id: nid }
});
}
let matchTypes: number;
const getType = async () => {
try {
const { data, code, msg } = await getMatchType(123);
if (code === 4) {
const matchTypes = data.matchTypes;
}
alert(msg);
} catch (error) {
console.log("catch error:", error);
}
};
keyof
keyof 关键字让我们能够获取一个 interface 的全部的 key
keyof 用于遍历某种类型的属性(可以操作接口、类以及基本数据类型)
interface IStu {
name: string;
age: number;
nest: [];
}
type deepKeys = keyof IStu; // 'name' | 'age' | 'nest'
// let dd: deepKeys = "323"; // err
let dd: deepKeys = "nest";
console.log(dd);
typeof
typeof 操作符用于获取变量的类型。因此这个操作符的后面接的始终是一个变量,且需要运用到类型定义当中
const letter = {
a: 1,
b: "ssss",
c: false
};
type TL = typeof letter;
const conditionData: TL = {
a: 2,
b: "here are some words",
c: true
};
TS 中的泛型工具
IPartial
创建一个符合目标接口其中的部分类型的新类型
type IPartial<T> = { [key in keyof T]?: T[key] };
interface IConditionData {
platId: string;
appId: string;
secretKey: string;
payType: number;
}
const con: Partial<IConditionData> = {
platId: "234"
// appIds: '234',
// secretKey: '234',
// payTypes: 123
};
type IConditionDataPartial = IPartial<IConditionData>;
const condition: IConditionDataPartial = {
platId: "234"
};
IPick<IConditionDatas, kkd>
从接口里选取几个属性类型做为一个新接口
IPick<IConditionDatas, kkd> 使用Pick来在Type中拾取keys对应的属性
interface IConditionData {
platId: string;
appId: string;
secretKey: string;
payType: number;
}
const con: keyof IConditionData = 'appId'
type IPick<T, K extends keyof T> = {
[key in K]: T[key]
}
type aps = 'appId' | 'platId' | 'secretKey'
type IConditionDataPick = IPick<IConditionData, aps>
const dataPick: IConditionDataPick = {
platId: '234',
appId: '234',
secretKey: '234'
}
console.log('dataPick: ', dataPick);
Record<keys, Type>
使用 Record 生成新类型,包含 keys 对应的属性键名,键值为 Type,常用来实现一种类型对另外一种类型的映射关系:
type Record<K extends keyof any, T> = { [P in K]: T };
type Page = "home" | "about" | "concat";
interface PageInfo {
title: string;
}
const nav: Record<Page, PageInfo> = {
home: { title: "231" },
about: { title: "231" },
concat: { title: "231" }
};
Exclude<Type, ExcludedUnion>
在 Type 中移除 ExcludedUnion 中的所有类型(差集)
type Exclude<T, U> = T extends U ? never : T;
interface I1 {
name3: string;
age: number;
}
interface I2 {
name: string;
age: number;
}
type T3 = Exclude<I1, I2>;
const dd: T3 = {
name3: "324",
age: 234
};
console.log("dd: ", dd);
interface IConditionData {
platId: string;
appId: string;
secretKey: string;
payType: number;
}
interface ICond {
platId: string;
}
const DD: Exclude<ICond, IConditionData> = {
platId: "2342",
appId: "2342",
appId3: "2342"
};
Omit
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
从 Type 中生成所有属性,并且移除 key 对应的属性
删除接口 Type 中 keys 中键组成新的接口
interface ITodo {
title: string;
description: string;
completed: string;
}
type TodoPreview = Omit<ITodo, "description">;
const todo: TodoPreview = {
title: "hahaha",
completed: "667"
};
IExtract
生成 Type 与 Union 的交集
第一个泛型参数 必须是继承 第二个的
type Extract<T, U> = T extends U ? T : never;
interface I1 {
name: string;
age: number;
// dta: number
}
interface I2 {
name: string;
age: number;
}
type T3 = Extract<I1, I2>;
const d: T3 = {
name: "234",
age: 234
// dta: 234
};
new 可实例化
interface ICallMeWithNewToGetString {
new (): string;
}
// 使用
declare const Foo: ICallMeWithNewToGetString;
const bar = new Foo(); // bar 被推断为 string 类型
本文随时可能更新,关注不迷路!