TS Mount Everest 002 [Study Notes]

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 &a…

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)

  1. Type A is passed in through a generic.
  2. If type A is a union type, it will be distributed.
  3. 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

(0)
Walker的头像Walker
上一篇 Mar 27, 2025 15:01
下一篇 Mar 27, 2025 15:01

Related Posts

  • Node In-depth and Easy to Understand (Sheng Siyuan Education) 002 [Study Notes]

    Node's package management and loading mechanisms: npm search xxx, npm view xxx, npm install xxx. Node.js file system operation APIs: Node.js's `fs` module provides synchronous (Sync) and callback/Promise-based asynchronous APIs for operating on local files and directories. Commonly used capabilities in daily development include reading, writing, appending, deleting, traversing directories, listening for changes, and so on. The following examples are based on C...

    Personal Nov 24, 2025
    22900
  • Go Engineering Systematic Course 014 [Study Notes]

    RocketMQ Quick Start. Go to our various configurations (podman) to see how it's installed. Introduction to Concepts: RocketMQ is a distributed messaging middleware open-sourced by Alibaba and an Apache top-level project. Core components: NameServer: Service discovery and routing; Broker: Message storage, delivery, and fetching; Producer: Message producer (sends messages); Consumer: Message consumer (subscribes to and consumes messages); Topic/Tag: Topic/...

    Personal Nov 25, 2025
    16500
  • Go Engineer Comprehensive Course: protoc-gen-validate Study Notes

    protoc-gen-validate: Introduction and Usage Guide ✅ What is protoc-gen-validate? protoc-gen-validate (PGV for short) is a Protocol Buffers plugin used to add validation logic for struct fields in generated Go code. It automatically generates validation code for each field by adding validation rules in .proto files, saving you the trouble of manually...

    Personal Nov 25, 2025
    1.3K00
  • In-depth Understanding of ES6 003 [Study Notes]

    Function parameter default values, as well as some details about the `arguments` object, how to use expressions as parameters, and the temporal dead zone for parameters. Previously, setting default values always relied on expressions containing the logical OR operator. When the preceding value was false, the latter value would always be returned. However, this became problematic if we passed 0 as an argument, requiring type verification. For example, `function makeRequest(url,timeout,callback){ timeout = t...`

    Personal Mar 8, 2025
    1.2K00
  • In-depth Understanding of ES6 001 [Study Notes]

    Block-Level Scope Binding
    Previously, `var` variable declarations, regardless of where they were declared, were considered to be declared at the top of their scope. Since functions are first-class citizens, the typical order was `function functionName()`, followed by `var variable`.

    Block-Level Declarations
    Block-level declarations are used to declare variables that cannot be accessed outside the scope of a specified block. Block-level scope exists in:
    - Inside functions
    - Within blocks (the region between `{` and `}`)

    Temporal Dead Zone
    When the JavaScript engine scans code and finds variable declarations, it either hoists them to the top of the scope...

    Personal Mar 8, 2025
    1.6K00
EN
简体中文 繁體中文 English
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹