Generics
/* * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git * @Date: 2025-03-19 10:42:31 * @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git * @LastEditTime: 2025-03-19 15:11:32 * @FilePath: /ts-classes/src/index.ts * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE */// Genericsclass Animal { constructor(public name: string, public age: number) {}}class Person { constructor(public name: string, public age1: number) {}}// In TypeScript, when using it, you can usefunction createInstance<T>(c: { new (...args: any[]): T }, ...args: any[]): T { return new c(...args);}// const animal = new Animal('dog', 12);// console.log(animal.name);// console.log(animal.age);const animal = createInstance<Animal>(Animal, 'dog', 12);const person = createInstance(Person, 'Tom', 12);// If the type is uncertain, we can determine it based on generics.// It's important to note that TypeScript does not execute; it only performs type checking during compilation.// Generate an array of corresponding length based on the provided data.function createArray<T>(length: number, value: T): T[] { return Array.from({ length }, () => value);}// Swap the values of two variables.type ISwap = <T, U>(tuple: [T, U]) => [U, T];interface ISwap2 { <T, U>(tuple: [T, U]): [U, T]; // Why are <T, U> written here instead of after the function? When generics are used, types can be passed and inferred, but the type is not determined during internal calls.}// Writing it at the beginning of the definition means passing parameters when using the type; writing it at the beginning of the function means passing parameters when calling the function.// Is the generic passed when using the type, or when calling the function?const swap: ISwap = (tuple) => { return [tuple[1], tuple[0]];};const swapArray = swap<number, string>([1, '2']);// Generics have default values and are used with some union types.type UnionType<T = boolean> = T | number | string;let union: UnionType = '123';// Generic constraints require that the passed parameters meet the requirements. A extends B means A is a subtype (or the same type) of B.interface ILength { length: number;}// What is a child? What is a parent?function getLength<T extends ILength>(value: T) { // As long as my object has a length property, it's fine. return value.length;}getLength('123');getLength({ length: 123 });function getValue<T, K extends keyof T>(obj: T, key: K) { return obj[key];}getValue({ name: '张三', age: 12 }, 'age');interface ILoginResponse<T> { code: number; message?: string; data: T;}interface ILoginData { token: string; userInfo: { name: string; age: number; }; roles: string[];}function toLogin<T>(response: ILoginResponse<T>) { return response.data;}const loginResponse = toLogin<ILoginData>({ code: 200, data: { token: '123', userInfo: { name: '张三', age: 12, }, roles: ['admin', 'user'], },});// Using generics in classesclass Cache<T> { private data: T[] = []; add(item: T) { this.data.push(item); }}const cache = new Cache<string>();cache.add('123');cache.add('456');export {};Intersection Types
// & Intersection Type | Union Type// Combines multiple types into one type.interface Person1 { handsome: string;}interface Person2 { high: string;}// A tall and handsome person (intersection type)type Person = Person1 & Person2;const person: Person = { // handsome: '帅', high: '高',};// An intersection type can be assigned to any supertype.export {};unkown
// unknown type// unknown is the safe type of any; when a generic type is not specified, it defaults to unknown.let a: unknown;// By default, unknown must be type-checked before it can be used (type checking or assertion).// Type assertionlet b = a as string;// unknown cannot directly call methods; assertion is required.function processInput(val: unknown) { if (typeof val === 'string') { console.log(val.toUpperCase()); } else if (typeof val === 'number') { console.log(val.toFixed(2)); } else { console.log('unknown'); }}let name: unknown = 'String字符串';// name is of unknown type, cannot directly call methods.(name as string).toUpperCase();// Characteristics of unknown in union or intersection typestype UnionType = unknown | string; // unknown typetype IntersectionType = unknown & string; // string typelet union: UnionType = 'String字符串';let intersection: IntersectionType = 'String字符串';export {};Conditional Types
// Conditional Types// Conditional types are a type operator in TypeScript that selects different types based on the result of a conditional expression.type ResStatusMessage<T extends number> = T extends 200 | 204 | 206 ? 'success' : 'error';// type R1 = ResStatusMessage<'abc'>; // The status code must be a number; passing it this way will not cause an error.type R2 = ResStatusMessage<200>; // The status code must be a number; passing it this way will not cause an error.type Condition<T, U> = T extends U ? 'success' : 'fail';type R3 = Condition<'abc', string>; // Type inferred as 'success'type R4 = Condition<'abc', number>; // Type inferred as 'fail'// Application of conditional types in functionsinterface Bird { fly: () => void;}interface Sky { name: '天空';}interface Fish { swim: () => void;}interface Water { name: '水';}type ConditionType<T> = T extends Bird ? Sky : Water;type R5 = ConditionType<Bird>; // Type inferred as Skytype R6 = ConditionType<Fish>; // Type inferred as Watertype FromatReturnType<T extends string | number> = T extends number ? number : T extends string ? string : never;// Generics generally represent whether the input is a definite (infinite) constraint, function overloading (finite).function sum<T extends number | string>(a: T, b: T): FromatReturnType<T> { // function sum<T extends number | string>(a: T, b: T):T { // If the return value is a number type, return a number type; if it's a string type, return a string type. But T cannot be written here as the return type. return a + (b as any); // T+T cannot be determined; two generics cannot perform data operations.}let r1 = sum(1, 2);let r2 = sum('1', '2');// Knowing conditional operators allows us to understand TypeScript's compatibility and type hierarchy.// Type Hierarchy// 1. Primitive Types// 2. Composite Types// 3. Function Types// 4. Class Types// Compatibility: allows assigning a value to another value.// Type Hierarchy: lower levels can be assigned to higher levels.Type Hierarchy Compatibility
// Type Hierarchy// 1. Primitive Types// 2. Composite Types// 3. Function Types// 4. Class Types// Knowing conditional operators allows us to understand TypeScript's compatibility and type hierarchy.// Compatibility: means a value can be assigned to another value.// Type Hierarchy: lower levels can be assigned to higher levels.// Theory: who is the parent type, who is the child type.type R1 = 'abc' extends string ? true : false;type R2 = 123 extends number ? true : false;type R3 = true extends boolean ? true : false;// In practice: 'abc' is a string type, 123 is a number type, true is a boolean type.let r1: string = 'abc';let r2: number = 123;type R4 = 'a' extends 'a' | 'b' | 'c' ? true : false;type R5 = 1 extends 1 | 2 | 3 ? true : false;type R6 = true extends true | false ? true : false;//// Literal types can be assigned to literal union types.let r4: 'a' | 'b' | 'c' = 'a';// Wrapper types can be assigned to primitive types.// let r5: string = new String('abc');// Wrapper Typestype R7 = string extends String ? true : false;type R8 = String extends string ? true : false;type R9 = number extends Number ? true : false;type R10 = Number extends number ? true : false;type R11 = boolean extends Boolean ? true : false;type R12 = Boolean extends boolean ? true : false;type R13 = Object extends any ? true : false;type R14 = Object extends unknown ? true : false;type R15 = never extends 'abc' ? true : false;// never is the smallest type.// Literal types can be assigned to literal union types.// Literal types can be assigned to primitive types.// Primitive types are subtypes of wrapper types.// any and unknown are the largest types.// never < literal type < literal union type < primitive type < wrapper type < Object < any unknowntype R16 = any extends string ? true : false; // The union type of true and false is boolean.// For the any type, it always returns a union type of success and failure.// Lower types can be assigned to higher types.// Structurally, an intersection type can be assigned to the pre-intersection type.// By default in TS, Object and object are the same size: Object extends object? true:false. The reverse is not true.// If it has more properties structurally, it can be assigned to you.// In terms of level, lower levels can be assigned to higher levels.export {};Utility Types
When normally judging types, you can use A extends B
Conditional type distribution (distribution feature is enabled by default)
- Type A is passed in through a generic.
- If type A is a union type, it will be distributed.
- The generic parameter A must be "naked" (not A & {}) to have type distribution.
// Application of conditional types in functions
interface Bird {
fly: () => void;
}
interface Sky {
name: '天空';
}
interface Fish {
swim: () => void;
}
interface Water {
name: '水';
}
type Condition = Fish | Bird extends Fish ? 'success' : 'fail'; // Type inferred as 'fail' (no distribution)
type Condition2<T> = T extends Fish ? 'success' : 'fail'; // Type inferred as 'success' (distribution)
type C3 = Condition2<Fish>; // success
// Distribution means comparing one by one.
// Condition2<Bird> fail
// Condition2<Fish> success
type C4 = Condition2<Bird | Fish>; // success | fail
// By default, sometimes we need to disable this distribution capability, which can lead to inaccurate judgments.
// How to eliminate this distribution type T & {}
type NoDistribute<T> = T & {};
// It's not a naked type; &{} loses the distribution capability. You can also add an array.
type Condition5<T, U> = [T] extends [U] ? true : false;
type R5 = Condition5<1 | 2, 1>; // false
// T & {} can eliminate distribution capability. Nothing happens; it's just to create a new type for T.
type IsNever<T> = T extends never ? true : false;
type R6 = IsNever<never>; // never directly returns never. Wrapping it in an array makes it true.
// Utility Types
// 1. Extract
type Extract<T, U> = T extends U ? T : never;
type R7 = Extract<1 | 2, 1>; // 1 (distribution judgment)
// 2. Exclude
type Exclude<T, U> = T extends U ? never : T;
type R8 = Exclude<1 | 2, 1>; // 2 (distribution judgment)
// 3. NonNullable
type NonNullable<T> = T extends null | undefined ? never : T;
type R9 = NonNullable<string | null>; // string
// 4. ReturnType (infer inference can infer a part of the return type in conditional types)
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R10 = ReturnType<() => string>; // string
// 5. Parameters
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type R11 = Parameters<(name: string, age: number) => string>; // [string, number]
// 6. ConstructorParameters
type ConstructorParameters<T> = T extends new (...args: infer P) => any
? P
: never;
type R12 = ConstructorParameters<Error>; // [message?: string | undefined]
// 7. Partial
type Partial<T> = {
[P in keyof T]?: T[P];
};
type R13 = Partial<{ name: string; age: number }>; // { name?: string; age?: number }
// Application of conditional types in arrays
type Swap<T> = T extends [infer A, infer B] ? [B, A] : T;
type R1 = Swap<['jw', 30]>; // 30 'jw'
// Application of conditional types in functions
type IsEqual<A, B> = A extends B ? (B extends A ? true : false) : false;
type R2 = IsEqual<1, 2>; // false
type R3 = IsEqual<1, 1>; // true
export {};
type Swap<T> = T extends [infer A, infer B] ? [B, A] : T;
type R1 = Swap<['jw', 30]>; // 30 'jw'
// Swap head and tail
type SwapHeadTail<T> = T extends [infer A, ...infer B, infer C]
? [C, ...B, A]
: T;
type R2 = SwapHeadTail<[1, 2, 3, 4, 5]>; // [5, 2, 3, 4, 1]
// Application of conditional types in strings
type IsString<T> = T extends string ? true : false;
type R14 = IsString<'jw'>; // true
// Application of conditional types in function overloading
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
type R15 = IsFunction<() => void>; // true
// Promise recursion
type PromiseReturnType<T> = T extends Promise<infer P>
? PromiseReturnType<P>
: T;
function getVal(): Promise<number> {
return new Promise((resolve) => {
resolve(100);
});
}
type R16 = PromiseReturnType<ReturnType<typeof getVal>>; // true
// Achieve recursive inference through infer
// Convert tuple to union type [number,boolean,string] => number | boolean | string
type TupleToUnion<T> = T extends [infer A, ...infer B]
? A | TupleToUnion<B>
: T;
type R17 = TupleToUnion<[number, boolean, string]>; // number | boolean | string
// Convert union type to tuple type
type UnionToTuple<T> = T extends infer P ? [P] : never;
type R18 = UnionToTuple<number | boolean | string>; // [number] | [boolean] | [string]
// Refactor type structure: Partial, Required, Readonly, Pick, Omit, Record
interface Person {
name: string;
age: number;
gender: string;
}
let person: Partial<Person> = {
name: 'jw',
};
function mixin<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
let p = mixin({ name: 'jw' }, { age: 100 });
let x = mixin(
{ name: 'jiangwen', age: 30, c: 3 },
{ name: 123, age: 30, b: 2 }
);
// An intersection type appeared, not merged as intended, e.g., retaining the latter content.
// In this case, apply removing properties of B from A.
function mixin2<T, U>(a: T, b: U): Omit<T, keyof U> & U {
return { ...a, ...b };
}
let y = mixin2(
{ name: 'jiangwen', age: 30, c: 3 },
{ name: 123, age: 30, b: 2 }
);
type nameType = (typeof y)['name'];
// Preserve the desired key-->value structure (some mapped types)
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type R19 = Record<'a' | 'b', string>; // { a: string; b: string }
export {};
Compatibility
// Duck typing, structural type checking
// Subtypes can be assigned to supertypes. From a structural perspective, TypeScript compares not the name of the type, but the properties and methods in its structure.
// Primitive type compatibility
let obj = {
toString: () => '123',
};
let str: string = '234';
obj = str; // From a safety perspective, if I satisfy all the properties you need, but have other properties, then I am not compatible.
// 2. Interface compatibility
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
age: number;
address: string;
}
let p: Person = { name: 'jw', age: 100 };
let a: Animal = { name: 'jw', age: 100, address: 'beijing' };
p = a; // From a safety perspective, if I satisfy all the properties you need, but have other properties, then I am not compatible.
// 3. Function compatibility
type Func = (a: number, b: number) => void;
let f1: Func = (a, b) => {};
let f2: Func = (a, b) => {}; // Parameters can only be fewer, not more (e.g., when we use forEach
f1 = f2; // From a safety perspective, if I satisfy all the properties you need, but have other parameters, then I am not compatible.
// 4. Class compatibility
class A {
constructor(public name: string) {}
}
class B {
constructor(public name: string, public age: number) {}
}
let a1 = new A('jw');
let b1 = new B('jw', 100);
a1 = b1; // From a safety perspective, if I satisfy all the properties you need, but have other properties, then I am not compatible.
// Function contravariance and covariance: function parameters are contravariant, return values are covariant.
class Parent {
house() {}
}
class Child extends Parent {
car() {
console.log('child car');
}
}
class GrandSon extends Child {
money() {
console.log('grandson money');
}
}
function fn(callback: (instance: Child) => Child) {
let child = new Child();
let ins = callback(child);
return ins;
}
// Why can the assigned function be of type Parent but not GrandSon? Internally, a Child type is passed, and when this instance is obtained, properties inaccessible to Child cannot be accessed.
fn((instance: Child) => {
return new Child();
});
// The return value should be a subtype of the return type (viewed from different perspectives).
fn((instance: Child) => {
return new GrandSon();
});
// For function compatibility, the number of parameters should be fewer, the passed type can be a supertype, and the return value can be a subtype.
// Derivation formula
type Arg<T> = (arg: T) => void;
type Return<T> = (arg: any) => T;
type ArgType = Arg<Parent> extends Arg<Child> ? true : false; // Contravariance
type ReturnType = Return<GrandSon> extends Return<Child> ? true : false; // Covariance
// Therefore, function parameters are contravariant, and return values are covariant.
// Enums do not have compatibility.
// When TypeScript compares type structures, it compares properties and methods. If all properties and methods match, then they are compatible.
// Achieve nominal typing through intersection types.
type Normal<T, K extends string> = T & { __type__: K };
type BTC = Normal<number, 'BTC'>;
type ETH = Normal<number, 'ETH'>;
type USDT = Normal<number, 'USDT'>;
let btc: BTC = 100 as BTC;
let eth: ETH = 200 as ETH;
let usdt: USDT = 300 as USDT;
function getVal(val: BTC) {
return val.valueOf();
}
getVal(btc); // Passing other types will result in an error.
export {};
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/4410