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 / 79136948
Accepted
Krzysztof Przybylski
Krzysztof Przybylski
Asked: 2024-10-29 19:08:04 +0800 CST2024-10-29 19:08:04 +0800 CST 2024-10-29 19:08:04 +0800 CST

Referindo-se a um tipo de objeto dentro do próprio objeto

  • 772

Preciso fazer referência ao typeof de um objeto dentro do próprio objeto, assim:

type Config<T extends Record<string, Config<T>> = any> = {
    name: string,
    value: number,
    children?: { [key: string]: Config<T> }
    visibilityFn?: VisibilityFn<T>
}

type VisibilityFn<T extends Record<string, Config>>= (children: T) => boolean



const config = {
    name: 'config',
    value: 1,
    children: {
        one: {
            name: 'one',
            value: 1,
            visibilityFn: (children: typeof config.children) => children.two.value > 3
        },
        two: {
            name: 'two',
            value: 2,
        },
    },
} satisfies Config

Não quero digitar explicitamente a forma dos filhos por meio de um tipo genérico, porque com estruturas mais complicadas e múltiplas reutilizações isso vai ficar velho muito em breve. Eu poderia simplesmente não digitar a função, mas obviamente não quero isso. A última opção que pensei é definir as funções de visibilidade fora da configuração, mas com estrutura aninhada defini-la para cada elemento é trabalhoso. Alguma ideia de como torná-la razoavelmente breve e estritamente tipada?

exemplo de playground datilografado

typescript
  • 2 2 respostas
  • 56 Views

2 respostas

  • Voted
  1. Salt
    2024-10-29T19:15:30+08:002024-10-29T19:15:30+08:00

    Você pode criar um auxiliar de tipo recursivo que permite obter essa inferência de tipo dinamicamente.

    type Config<TChildren = Record<string, never>> = {
      name: string;
      value: number;
      children?: TChildren;
      visibilityFn?: (children: TChildren) => boolean;
    };
    
    // Recursive definition for nested configurations
    type ConfigTree = {
      [key: string]: Config<ConfigTree>;
    };
    
    const config: Config<ConfigTree> = {
      name: 'config',
      value: 1,
      children: {
        one: {
          name: 'one',
          value: 1,
          visibilityFn: (children) => children.two.value > 3,
        },
        two: {
          name: 'two',
          value: 2,
        },
      },
    };
    
    // Type inference works here
    if (config.children) {
      const visible = config.children.one.visibilityFn?.(config.children);
      console.log(visible);
    }
    
    • 0
  2. Best Answer
    jcalz
    2024-10-30T22:05:40+08:002024-10-30T22:05:40+08:00

    Geralmente, você não pode se referir a typeof xdentro da definição para o valor xsem entrar em conflito com os avisos de circularidade do TypeScript. Na verdade, o que você precisa fazer é dar Config<T>uma definição genérica recursiva adequada para que ela possa realmente representar o tipo que você precisa automaticamente pretende transmitir, onde em cada nível da árvore, o visibilityFnparâmetro de retorno de chamada do método opcional corresponde ao tipo da childrenpropriedade do pai daquele nível. Algo assim:

    type Config<T, TP = {}> = {
        name: string,
        value: number,
        children?: ConfigChildren<T>
        visibilityFn?: (children: ConfigChildren<TP>) => boolean
    }
    
    type ConfigChildren<T> = { [K in keyof T]: Config<T[K], T> }
    

    Aqui TPestá o tipo pai, que para o nível superior deve ser apenas o objeto de tipo vazio (é por isso que escolhi isso como o argumento de tipo padrão para TP). Então, se childrenfor do tipo ConfigChildren<T>, então visibilityFnrecebe um parâmetro do tipo ConfigChildren<TP>. Então ConfigChildrené apenas um tipo mapeado onde cada propriedade está Config<T[K], T>(já que no próximo nível abaixo, Tse torna o pai da T[K]propriedade).

    Agora você não pode usar o satisfiesoperador diretamente porque não pode facilmente nomear o tipo configque ele deve satisfazer sem se referir a si mesmo. Ou seja, você precisaria escrever satisfies Config<{one: any, two: { three: any} }>ou algo assim, e principalmente anula o propósito do que você está fazendo. Se você estivesse feliz escrevendo esse tipo de propriedade como um argumento de tipo, então você teria apenas anotado seu tipo de parâmetro explicitamente em vez de tentar escrever typeof config.children.

    Então, vamos abandonar isso satisfiesem favor de uma função auxiliar genérica que, com sorte, infere esse tipo de argumento para você:

    const asConfig = <T,>(c: Config<T>) => c;
    

    E agora você pode chamá-lo em seu literal de objeto:

    const config = asConfig({
        name: 'config',
        value: 1,
        children: ({
            one: {
                name: 'one',
                value: 1,
                children: {},
                visibilityFn: (children) => (children.two.children?.three.value ?? 0) > 3
                //             ^? (parameter) children: ConfigChildren<
                //                  {  one: unknown; two: { three: unknown; };}>
            },
            two: {
                name: 'two',
                value: 2,
                children: ({
                    three: {
                        name: 'three',
                        value: 3,
                        children: {}
                    }
                })
            },
        }),
    })
    /* const config: Config<{
        one: unknown;
        two: {
            three: unknown;
        };
    }, {}> */
    

    Aqui, o argumento de tipo Té inferido como {one: unknown; two: {three: unknown}}, e, portanto, dentro de config.one.visibilityFn, o childrenparâmetro é contextualmente tipado como ConfigChildren<T>para isso T, e, portanto, sabe-se que children.two.children, se existir, tem uma threepropriedade. (Observe que a parte "se existir" é porque childrené definido como uma propriedade opcional de config. Se você precisar de algo mais específico para que o TypeScript saiba quando ele aparece e não aparece, você precisará fazer uma refatoração. Mas os detalhes de como esse tipo recursivo deve ser ajustado para seu caso de uso estão fora do escopo aqui.)

    Isso funciona até certo ponto, mas esteja ciente de que o TypeScript não tem a capacidade de inferir tipos arbitrariamente complicados. Pode ser que em alguma profundidade da sua árvore você precise envolver algo em outra asConfig()chamada para que os níveis mais baixos sejam inferidos separadamente dos superiores. Ou você pode se ver tendo que anotar parâmetros de tipo que você realmente prefere que o TypeScript infira para você. Isso pode acontecer, especialmente quando genéricos e tipagem contextual precisam ocorrer simultaneamente ou em alguma ordem específica. Há uma solicitação de recurso em microsoft/TypeScript#47599 para melhorar esse tipo de inferência, mas sempre haverá casos em que isso simplesmente não pode acontecer. Se você se deparar com isso, será muito melhor apenas escrever o tipo de que precisa em vez de tentar forçar o TypeScript a fazer coisas que ele não consegue fazer bem.

    Link do playground para o código

    • 0

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

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

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 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

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 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
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +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
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +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