AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79549386
Accepted
nullromo
nullromo
Asked: 2025-04-02 07:38:17 +0800 CST2025-04-02 07:38:17 +0800 CST 2025-04-02 07:38:17 +0800 CST

É possível impedir que o TypeScript quebre uma união de matrizes em uma matriz de uniões após um filtro?

  • 772

Eu tenho um number[] | string[]e quero filtrá-lo. O problema é que a função de filtro muda o tipo para (number | string)[]instead.

// list of numbers OR list of strings
let myList = Math.random() < 0.5 ? [1, 2, 3] : ["a", "b", "c"];

// filter the list
const filtered = myList.filter((x) => { return true; });

// oh no, the result is Array<number | string> instead of (number[] | string[])
myList = filtered;

Erro na última linha:

Type '(string | number)[]' is not assignable to type 'number[] | string[]'.
  Type '(string | number)[]' is not assignable to type 'number[]'.
    Type 'string | number' is not assignable to type 'number'.
      Type 'string' is not assignable to type 'number'.(2322)

Como posso expressar o fato de que a matriz não pode ser mista , mas pode conter qualquer tipo de elemento?

Link do parque infantil

typescript
  • 2 2 respostas
  • 96 Views

2 respostas

  • Voted
  1. Best Answer
    jcalz
    2025-04-03T02:29:00+08:002025-04-03T02:29:00+08:00

    O TypeScript não distribui automaticamente os tipos de retorno de chamadas de função/método genéricos entre uniões em seus argumentos. Conceitualmente, uma função do formulário <T>(i: I<T>)=>O<T>quando chamada com um argumento do tipo I<T1> | I<T2> | I<T3>deve produzir uma saída do tipo O<T1> | O<T2> | O<T3>. Mas agora isso não acontece. Ou tal chamada de função é rejeitada imediatamente ou é aceita, mas produz uma saída do tipo O<T1 | T2 | T3>. Há uma solicitação de recurso em microsoft/TypeScript#52295 para distribuir automaticamente a aplicação de função sobre argumentos do tipo union, mas não é um recurso muito procurado e implementá-lo degradaria significativamente o desempenho do compilador. Então, a menos que uma função genérica seja explicitamente escrita para distribuir entre uniões em suas entradas, isso não acontecerá.

    As tipagens da biblioteca TypeScript se Array.prototype.filter()parecem com estas:

    interface Array<T> {
      filter<S extends T>(predicate: (v: T, i: number, a: T[]) => v is S, th?: any): S[];
      filter(predicate: (v: T, index: i, a: T[]) => unknown, th?: any): T[];
    }
    

    Esse é um método sobrecarregado com múltiplas assinaturas de chamada. A primeira assinatura de chamada lida com a situação em que o predicateage como uma função de proteção de tipo e permite que o tipo de array de saída seja mais estreito do que a entrada that (portanto, ["a", "b", 1, 2, "c"].filter(x => typeof x === "string")é do tipo string[]and not (string | number)[]). A segunda assinatura de chamada é o caso usual em que o predicado não pode ser usado para restringir os tipos de elementos.

    Nenhuma dessas assinaturas tenta distribuir sobre uniões no array no qual o método é chamado. Se você quiser um comportamento como esse, precisará escrevê-lo explicitamente você mesmo.


    Se você quiser que o comportamento union-distribution aconteça automaticamente para todos os arrays na sua base de código, você pode mesclar novas assinaturas de chamada de sobrecarga na Array<T>interface global (e talvez ReadonlyArray<T>também). Talvez assim:

    interface Array<T> {
        filter<A extends Array<T>, S extends T>(
            this: A, predicate: (v: A[number], i: number, a: A) => v is S
        ): A extends Array<infer T> ? (T & S)[] : never;
    
        filter<A extends Array<T>>(
            this: A, predicate: (v: A[number], i: number, a: A) => unknown
        ): A extends Array<infer T> ? T[] : never;
    }
    

    Aqui estamos adicionando um novo parâmetro de tipo genérico Aao filter()método, representando o tipo do array no qual você o chama. Para inferir esse tipo corretamente, o método tem um thisparâmetro do tipo A. Um thisparâmetro é um parâmetro fingido que não existe em tempo de execução; ele apenas representa o tipo do objeto no qual o método é chamado. Então, quando você chama myList.filter(⋯), deve ser que Aé inferido como typeof myList.

    Então há muito malabarismo de tipos. O tipo A extends Array<infer T> ? ⋯ : never;é um tipo condicional distributivo que produz explicitamente uma união de ⋯saídas quando Aé uma entrada de união. Para o caso de proteção de tipo, temos que tentar estreitar cada membro da união de uma matriz de seu tipo de elemento original para um que também seja atribuível ao tipo protegido por tipo S, daí a interseção . Para o caso normal, ainda temos que distribuir sobre o tipo de elemento regular. Você pode se perguntar por que não retornamos apenas A. Isso porque Apode ser um tipo de tupla como [string, number, boolean], e não queremos retornar filter()esse mesmo tipo. Um tipo como (string | number | boolean)[]é mais apropriado.

    Isso resulta no seguinte comportamento:

    let myList = Math.random() < 0.5 ? [1, 2, 3] : ["a", "b", "c"];
    const filtered0 = myList.filter(x => true);
    //    ^? const filtered0: string[] | number[]
    const filtered1 = myList.filter(x => typeof x === "string");
    //    ^? const filtered1: string[] | never[]
    const filtered2 = ([1, 2, 3, 4] as const).filter(x => Math.random() < 0.5);
    //    ^? const filtered2: (1 | 2 | 3 | 4)[]
    const filtered3 = ([1, 2, 3, "a", "b", "c"] as const).filter(x => typeof x === "string");
    //    ^? const filtered3: ("a" | "b" | "c")[]
    

    Isso funciona muito bem, mas pode haver alguns casos extremos bizarros que eu não previ. Se você não quiser aplicar isso a toda a sua base de código, você pode querer apenas escrever uma filter()função com essas assinaturas de chamada como um wrapper:

    function filter<A extends Array<any>, S extends A[number]>(
        arr: A, predicate: (v: A[number], i: number, a: A) => v is S
    ): A extends Array<infer T> ? (T & S)[] : never;
    
    function filter<A extends Array<any>>(
        arr: A, predicate: (v: A[number], i: number, a: A) => unknown
    ): A extends Array<infer T> ? T[] : never;
    
    function filter(arr: any[], predicate: (v: any, i: number, a: any[]) => any) {
        return arr.filter(predicate)
    }
    

    Isso é um pouco mais simples porque não precisamos brincar com thisparâmetros e inferência. O TypeScript pode inferir Adiretamente do arrparâmetro real. O comportamento é o mesmo:

    let myList = Math.random() < 0.5 ? [1, 2, 3] : ["a", "b", "c"];
    const filtered0 = filter(myList, x => true);
    //    ^? const filtered0: string[] | number[]
    const filtered1 = filter(myList, x => typeof x === "string");
    //    ^? const filtered1: string[] | never[]
    const filtered2 = filter([1, 2, 3, 4] as const, x => Math.random() < 0.5);
    //    ^? const filtered2: (1 | 2 | 3 | 4)[]
    const filtered3 = filter([1, 2, 3, "a", "b", "c"] as const, x => typeof x === "string");
    //    ^? const filtered3: ("a" | "b" | "c")[]
    

    Mas agora você não precisa se preocupar com o filter()comportamento estranho do método de array nativo em alguma parte remota da sua base de código.


    Mas mesmo isso é um pouco complicado e ainda pode ter casos extremos. Se você vai fazer muita filtragem de uniões de arrays, então posso ver que vale a pena. Mas se você só vai fazer isso uma ou duas vezes na sua base de código, você provavelmente deve usar uma asserção de tipo e seguir em frente:

    const filtered0 = myList.filter(x => true) as string[] | number[];
    

    É de longe a abordagem mais simples e, embora não seja verificável quanto ao tipo, a complexidade das outras abordagens é provavelmente mais propensa a erros. Quero dizer, por um lado, você está dizendo ao TypeScript que filtered0é um string[] | number[], e, por outro lado, você está tentando dizer ao TypeScript que toda chamada para filter()faz essa coisa complicada com uniões no nível do tipo. Se você não está muito confiante de que a coisa complicada está correta, então provavelmente é mais seguro apenas verificar suas asserções.

    Link do playground para o código

    • 2
  2. Alexander Nenashev
    2025-04-02T18:29:50+08:002025-04-02T18:29:50+08:00

    Infelizmente, algumas definições de métodos JS/DOM padrão são tipadas de forma mais ampla do que o esperado. Você poderia aumentar Arrayassim:

    Parque infantil

    // list of numbers OR list of strings
    let myList = Math.random() < 0.5 ? [1, 2, 3] : ["a", "b", "c"];
    
    interface Array<T>{
        filter<A extends T[], This>(this: A, cb: (this: This, item: T, idx: number, arr: A) => unknown, thisArg?: This): A;
    }
    
    // filter the list
    const filtered = myList.filter(function (x){
        return this.test;
    }, {test: true});
    
    myList = filtered; // ok
    
    • 1

relate perguntas

  • Por que usamos colchetes `[]` em condicionais?

  • Problema do Nestjs em relação aos módulos

  • Como obter metadados datilografados de um arquivo?

  • Como converter array em tipo const de retorno de objeto assim?

  • "Nenhuma sobrecarga corresponde" minha chamada Object.assign(); como posso corrigir isso?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve