A compreensão e uso de inferir para uso avançado de texto digitado

Maravilhoso no passado

Prefácio

Eu nunca usei inferir antes , ou é apenas returnType . Você não precisa usar inferir . Esses tutoriais online apenas fornecem exemplos e não fornecem cenários específicos, então as pessoas não podem entender essas coisas bem.

Distribuição de tipo

Para inferir , é melhor falar primeiro sobre distribuição de tipo, embora eles não sejam muito relacionados, mas se você combinar inferir com distribuição de tipo, as pessoas pensarão que essa pessoa está no nível ts . Quanto a conceitos como covariância e contravariância , é mais fácil confundir as pessoas e pode ser dominado posteriormente.

Já aprendi isso antes, mas não consigo entender bem o momento de seu uso e não sei como usá-lo.Portanto, é totalmente dois estados que pode ser entendido por outras pessoas e pode ser usado por mim mesmo.

Primeiro, olhe para um exemplo básico de distribuição de tipo:

interface Fish {
    fish: string
}
interface Water {
    water: string
}
interface Bird {
    bird: string
}
interface Sky {
    sky: string
}
//naked type
type Condition<T> = T extends Fish ? Water : Sky;


let condition1: Condition<Fish | Bird> = { water: '水' };
let condition2: Condition<Fish | Bird> = { sky: '天空' };

Acredito que esse exemplo seja fácil de entender para todos, mas não sei quando e como usá-lo na prática.

Uma característica desse exemplo é que o tipo genérico transmitido nos tipos definidos na condição1 e na condição2 a seguir não é igual ao tipo atribuído posteriormente.

Em outras palavras, a distribuição de tipo geralmente é usada para conhecer primeiro o tipo conhecido, e o tipo do valor atribuído será julgado com base nessa distribuição para deduzir o tipo correspondente.

À primeira vista, parece que ainda não há uso para ovos. Por exemplo, condição 1. Eu sei o tipo. Acabei de escrever um tipo Céu | Água não é bom? Por que os segundos têm uma distribuição de tipo?

O exemplo acima é realmente inútil, mas se for considerado que o herdado também é genérico, você pode remover rapidamente alguns tipos sem ter que redefini-los você mesmo: (embora muitos deles sejam integrados)

type Diff<T, U> = T extends U ? never : T;


type R = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"


type Filter<T, U> = T extends U ? T : never;
type R1 = Filter<string | number | boolean, number>;

Uma vez que existem embutidos, não é inútil. . . Portanto, ele precisa ser usado em conjunto com inferir para ver onde está.

Um estudo preliminar de inferência

Infer, todos devem saber que returnType é feito por inferir. O código é assim:

type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;

À primeira vista, parece um pouco difícil de entender, mas na verdade é muito fácil de entender depois de um olhar mais atento.Também é uma distribuição de tipo.

Depois de aprender isso, muitas pessoas podem apenas saber que existe essa coisa, mas quando usam o Infer, eu não sei de nada e sou assim. Mais tarde, quando estava na aula novamente, descobri que essa inferência é na verdade equivalente a um marcador, ou seja, eu não sei. Use inferir X para dar a ele um lugar e, em seguida, combine o tipo para distribuir, você pode pregar peças. Naquela época, alguns amigos perguntaram: Não pode ts não inferir automaticamente o tipo? Por que é necessário inferir X para inferir o tipo. Porra , essa é uma ótima pergunta, essa é a chave para entender o Infer .

Vamos combinar um exemplo para ilustrar:

export {}
type Parameters<T> = T extends (...args: infer R) => any ? R : any;
type T0 = Parameters<() => string>;  // []
type T1 = Parameters<(s: string) => void>;  // [string]
type T2 = Parameters<(<T>(arg: T) => T)>;  // [unknown]

Este parâmetro também está embutido. Como você pode ver, também é uma distribuição de tipo. A diferença de returnType é que o marcador de posição de inferir X vai para o parâmetro para definir o tipo.

Se substituirmos inferir R por um tipo conhecido, a distribuição desse tipo não será muito diferente da demonstração no início:

type Parameters<T> = T extends (...args:string[]) => any ? string[] : any;
type T0 = Parameters<() => string>; 

Se você não alterar para um tipo conhecido, obterá um erro se escrever apenas R e não inferir, porque você não sabe o que é R.

E daí se você passar por genéricos? É uma pena que args deva ser um tipo de array, portanto, as condições para passá-lo com os genéricos devem ser limitadas:

type Parameters<T,R extends Array<any>> = T extends (...args:R) => any ? R : any;


type T0 = Parameters<() => string,string[]>; 

Pode-se verificar que esta transmissão não é muito diferente da transmissão do tipo conhecido, pois quando o segundo tipo genérico é passado, conhecemos esse tipo, então neste caso não é muito útil, a menos que o tipo genérico seja passado. É outra pessoa, então, quando escrevemos esta biblioteca, podemos obter tipos definidos pelo usuário. Neste momento, é útil.

Desta forma, você pode descobrir que inferir pode ocupar qualquer posição na derivação de tipo, e o tipo da derivação final pode usar o tipo necessário no meio. Você pode olhar para este exemplo para aprofundar sua compreensão:

  type T1 = { name: string };
  type T2 = { age: number };
  
  type UnionToInterp<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
  type T3 = UnionToInterp<{ a: (x: T1) => void; b: (x: T2) => void }>; // T1 & T2

Este exemplo é aquele inferir obtém os parâmetros, os parâmetros das duas funções, por que duas sairão do tipo cruzado, aqui está a covariância, então é o tipo cruzado.

Vejamos um exemplo mais difícil, que vem do recrutamento Leetcode:

https://github.com/LeetCode-OpenSource/hire/blob/master/typescript_zh.md

O título é este:

interface Action<T> {
    payload?: T;
    type: string;
  }
  
  class EffectModule {
    count = 1;
    message = "hello!";
  
    delay(input: Promise<number>) {
      return input.then(i => ({
        payload: `hello ${i}!`,
        type: 'delay'
      }));
    }
  
    setMessage(action: Action<Date>) {
      return {
        payload: action.payload!.getMilliseconds(),
        type: "set-message"
      };
    }
  }
  
  // 修改 Connect 的类型,让 connected 的类型变成预期的类型
  type Connect = (module: EffectModule) => any;
  
  const connect: Connect = m => ({
    delay: (input: number) => ({
      type: 'delay',
      payload: `hello 2`
    }),
    setMessage: (input: Date) => ({
      type: "set-message",
      payload: input.getMilliseconds()
    })
  });
  
  type Connected = {
    delay(input: number): Action<string>;
    setMessage(action: Date): Action<number>;
  };
  
  export const connected: Connected = connect(new EffectModule());

É necessário modificar o any para retornar o tipo correto e este tipo deve ser o mesmo que conectado.

Alguns alunos disseram, basta alterar "qualquer" para "conectado". Se fosse tão simples, esta questão não seria levantada. Isso deve ser enviado por você, e o tipo conectado é um método na instância EffectModule e os parâmetros e retornos internos foram modificados.

Como fazer esta pergunta, primeiro passo a passo, primeiro extraia o método effectModule, caso contrário, não há próximo passo.

Não existe um método pronto para extrair a classe, e não deve ser keyof EffectModule, porque existem outras coisas, como excluir outras coisas? É para usar a distribuição de tipo e a classe pode assumir valores. Se for uma função, extraia-a, caso contrário, ela não extrairá:

  type MethodName<T> = {[F in keyof T]:T[F] extends Function ? F:never}[keyof T]  type EE =  MethodName<EffectModule>

Ao mesmo tempo, se o valor for never, keyof não retornará. Este parágrafo é bastante esclarecedor, porque muitas vezes, eu quero fazer um tipo de julgamento circular e depois fazer uma seleção. Este é um bom exemplo.

Após obter o nome, para modificar o método, é necessário:

asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>> asyncMethod<T, U>(input: T): Action<U> syncMethod<T, U>(action: Action<T>): Action<U>  syncMethod<T, U>(action: T): Action<U>

Isso é fornecido pelo título, copie-o diretamente:

type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>> type asyncMethodConnect<T, U> = (input: T) => Action<U> type syncMethod<T, U> = (action: Action<T>) => Action<U> type syncMethodConnect<T, U> = (action: T) => Action<U> 

Em seguida, você precisa fazer uma distribuição de tipo para determinar qual método é e depois distribuir para qual método:

type EffectMethodAssign<T> = T extends asyncMethod<infer U, infer V>      ? asyncMethodConnect<U, V>     : T extends syncMethod<infer U, infer V>     ? syncMethodConnect<U, V>     : never

Esse parágrafo é muito simples, é o julgamento da distribuição, o tipo genérico usa infer para ocupar a posição ok.

Finalmente, modifique a conexão e pronto.

  type Connect = (module: EffectModule) => {
      [F in MethodName<typeof module>]:EffectMethodAssign<typeof module[F]>
  }

Acho que você gosta

Origin blog.csdn.net/KlausLily/article/details/108878205
Recomendado
Clasificación