Typescript: Proposta: Obtenha o tipo de qualquer expressão com typeof

Criado em 25 jan. 2016  ·  157Comentários  ·  Fonte: microsoft/TypeScript

Implementação de trabalho para esta proposta

Experimente: npm install yortus-typescript-typeof

Veja a diferença: aqui .

Cenário do problema

A inferência de tipos do TypeScript cobre a maioria dos casos muito bem. No entanto, existem algumas situações em que não há uma maneira óbvia de fazer referência a um tipo anônimo, mesmo que o compilador seja capaz de inferi-lo. Alguns exemplos:

Eu tenho uma coleção fortemente tipada, mas o tipo de elemento é anônimo/desconhecido, como posso referenciar o tipo de elemento? (#3749)
// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data[0] /* ERROR */) {...}

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42, ... };
type Thing2Type = typeof things['thing-2']; // ERROR

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0); // ERROR
Uma função retorna um tipo local/anônimo/inacessível, como posso referenciar esse tipo de retorno? (#4233, #6179, #6239)
// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(http) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return new MyAPI($http);
}
function augmentAPI(api: MyAPI /* ERROR */) {...}
Tenho uma interface com uma forma anônima complexa, como posso me referir aos tipos de suas propriedades e subpropriedades? (#4555, #4640)
// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof MyInterface.prop1.big.complex; // ERROR
}

Por que precisamos fazer referência a tipos anônimos/inferidos?

Um exemplo é declarar uma função que recebe um tipo anônimo como parâmetro. Precisamos referenciar o tipo de alguma forma na anotação de tipo do parâmetro, caso contrário o parâmetro terá que ser digitado como any .

Soluções alternativas atuais

Declare uma variável fictícia com um inicializador que infere o tipo desejado sem avaliar a expressão (isso é importante porque não queremos efeitos colaterais de tempo de execução, apenas inferência de tipo). Por exemplo:

let dummyReturnVal = null && someFunction(0, ''); // NB: someFunction is never called!
let ReturnType = typeof dummyReturnVal;           // Now we have a reference to the return type

Esta solução alternativa tem algumas desvantagens:

  • muito claramente um kludge, pouco claro para os leitores
  • poluição do identificador (deve introduzir uma variável como dummyReturnValue )
  • não funciona em contextos ambientais, porque requer uma declaração imperativa

Solução proposta

_(NB: Esta solução já foi sugerida no nº 4233, mas esse problema está marcado como 'Precisa de proposta' e há vários outros problemas intimamente relacionados, daí este problema separado.)_

Permita que o operando de typeof seja uma expressão arbitrária. Isso já é permitido para typeof expr em uma posição de valor como if (typeof foo() === 'string') . Mas esta proposta também permite uma expressão arbitrária quando typeof é usado em uma posição de tipo como uma consulta de tipo, por exemplo, type ElemType = typeof list[0] .

Esta proposta já está alinhada com a redação atual da especificação:

As consultas de tipo são úteis para capturar tipos anônimos gerados por várias construções, como literais de objeto, declarações de função e declarações de namespace.

Portanto, esta proposta está apenas estendendo essa utilidade para as situações atualmente não atendidas, como nos exemplos acima.

Sintaxe e Semântica

A semântica é exatamente como já declarada na especificação 4.18.6 :

O operador 'typeof' pega um operando de qualquer tipo e produz um valor do tipo primitivo String. Em posições onde um tipo é esperado, 'typeof' também pode ser usado em uma consulta de tipo (seção 3.8.10) para produzir o tipo de uma expressão.

A diferença proposta refere-se à seção 3.8.10 citada abaixo, onde o texto tachado seria removido e o texto em negrito adicionado:

Uma consulta de tipo consiste na palavra-chave typeof seguida por uma expressão. A expressão é processada como uma expressão identificadora (seção 4.3) ou expressão de acesso à propriedade (seção 4.13) uma expressão unária , cujo tipo ampliado (seção 3.12) se torna o resultado. Semelhante a outras construções de tipagem estática, as consultas de tipo são apagadas do código JavaScript gerado e não adicionam sobrecarga de tempo de execução.

Um ponto que deve ser enfatizado (que eu achava que também estava na especificação, mas não consigo encontrar) é que as consultas de tipo não avaliam seu operando. Isso é verdade atualmente e permaneceria verdadeiro para expressões mais complexas.

Esta proposta não introduz nenhuma sintaxe nova, apenas torna typeof menos restritivo nos tipos de expressões que pode consultar.

Exemplos

// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data[0]) {...} // OK: item type is {raw:number, square:number}

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42, ... };
type Thing2Type = typeof things['thing-2']; // OK: Thing2Type is number

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0); // OK: ItemType is HTMLLIElement

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(http) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return new MyAPI($http);
}
type MyAPI = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type
function augmentAPI(api: MyAPI) {...} // OK

// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}
}

Discussão de prós/contras

Contra : Estética de sintaxe ruim. Sintaxes alternativas para casos individuais foram sugeridas em #6179, #6239, #4555 e #4640.

Para : Outras sintaxes podem parecer melhores para seus casos específicos, mas são todas diferentes umas das outras e cada uma resolve apenas um problema específico. Esta proposta resolve os problemas levantados em todas essas questões, e o desenvolvedor não precisa aprender nenhuma(s) nova(s) sintaxe(s).

Contra : Uma expressão em uma posição de tipo é confusa.

Para : TypeScript já sobrecarrega typeof com dois significados, como uma consulta de tipo já aceita uma expressão em uma posição de tipo e obtém seu tipo sem avaliá-lo. Isso apenas relaxa as restrições sobre o que essa expressão pode ser para que ela possa resolver os problemas levantados nesta edição.

Contra : Isso pode ser usado de forma abusiva para escrever grandes consultas do tipo multilinhas.

Para : Não há uma boa razão para fazer isso em uma consulta de tipo, mas há boas razões para permitir expressões mais complexas. Isso é basicamente a habilitação versus direção de Martin Fowler.

Impacto do projeto, perguntas e trabalho adicional

Compatibilidade

Esta é uma mudança puramente compatível com versões anteriores. Todo o código existente não é afetado. Usar os recursos adicionais de typeof é opt-in.

atuação

Olhando para o diff você pode ver que as mudanças são muito pequenas. O compilador já conhece os tipos que estão sendo consultados, isso apenas os apresenta ao desenvolvedor. Eu esperaria um impacto insignificante no desempenho, mas não sei como testar isso.

Ferramentas

Configurei o VS Code para usar uma versão do TypeScript com esta proposta implementada como seu serviço de linguagem, e todo o realce de sintaxe e intellisense é impecável até onde eu testei.

Expressões complexas podem ocorrer em arquivos .d.ts

O operando typeof pode ser qualquer expressão, incluindo um IIFE, ou uma expressão de classe completa com corpos de métodos, etc. Não consigo pensar em nenhum motivo para fazer isso, simplesmente não é mais um erro, mesmo dentro um arquivo .d.ts ( typeof pode ser usado - e é útil - em contextos ambientais). Portanto, uma consequência dessa proposta é que "declarações não podem aparecer em contextos ambientais" não é mais estritamente verdadeira.

Tipos recursivos são tratados de forma robusta

O compilador parece já ter toda a lógica necessária para lidar com coisas assim:

function foo<X,Y>(x: X, y: Y) {
    var result: typeof foo(x, y); // ERROR: 'result' is referenced in its own type annotation
    return result;
}
Pode consultar o tipo de retorno de uma função sobrecarregada

Não é ambíguo; ele escolhe a sobrecarga que corresponde à expressão da consulta:

declare function foo(a: boolean): string;
declare function foo(a: number): any[];
type P = typeof foo(0);    // P is any[]
type Q = typeof foo(true); // Q is string
Suggestion Too Complex

Comentários muito úteis

Abriu um PR em #17961.

Todos 157 comentários

Para quem deseja uma maneira rápida de brincar com isso no VS Code com intellisense etc, aqui está um repositório de playground .

tipo P = tipo de foo(0); // P é qualquer[]
tipo Q = tipo de foo(true); // Q é string

Acho que usar tipos como argumento em vez de valores é uma sintaxe mais válida.

type P = typeof foo(number);    // P is any[]
type Q = typeof foo(boolean); // Q is string

É mais claro que a função não está sendo chamada, porque você fornece tipos e não valores como argumentos. O outro ponto, é que é menos ambíguo. Algumas pessoas usarão typeof foo(false) , enquanto outras usarão typeof foo(true) . Se você tiver tipos como argumentos, as pessoas só poderão escrever typeof foo(boolean) .

@tinganho exatamente!
Embora ainda possamos escrever typeof foo("abc") com #5185
aqui "abc" é o tipo de string singleton

@tinganho Andei pensando na sua ideia e vejo algumas coisas que prefiro nessa proposta, e outras que prefiro na sua sugestão. Sua sugestão é boa pelas razões que você deu (sintaxe mais simples e clara, menos ambígua, parece menos com uma chamada de função). A coisa que eu prefiro na minha proposta é que ela não introduz nenhuma sintaxe nova, então não adiciona nenhuma complicação ao analisador/verificador, e também suporta cenários mais complexos onde você não tem nomes de tipo simples para os argumentos.

Eu estava pensando, e se houvesse uma maneira muito abreviada de escrever algo como sua sintaxe foo(number) mas usando a mecânica de análise de expressão existente? Então, como experimento, introduzi uma nova expressão: _unary as_. Você pode simplesmente escrever as T e isso é uma abreviação de (null as T) . Você está basicamente dizendo: _'Não me importo com o valor, mas quero que a expressão tenha o tipo X'_.

Com essa mudança (que implementei no repositório playground ), você pode escrever algo muito mais próximo da sintaxe sugerida, mas ainda é analisada como uma expressão comum:

type P = typeof foo(as number);    // P is any[]
type Q = typeof foo(as boolean); // Q is string

let prop2: typeof (as MyInterface).prop1.big.complex; // prop2 type is {anonymous: {type: {}}}

Este foi apenas um experimento rápido. Uma sintaxe equivalente poderia ser (mas eu não implementei isso):

type P = typeof foo(<number>);    // P is any[]
type Q = typeof foo(<boolean>); // Q is string

let prop2: typeof (<MyInterface>).prop1.big.complex; // prop2 type is {anonymous: {type: {}}}

A segunda sintaxe pode ser chamada de _asserção de tipo nulo_, com a expressão <T> sendo uma abreviação de (<T> null) .

@yortus , passamos algum tempo na semana passada falando sobre essa proposta. desculpe por não postar antes. o consenso foi 1. temos o problema de não podermos nos referir a alguns tipos, por exemplo, retorno de uma função ou tipo de instância de uma expressão de classe. e 2. adicionar expressões em uma posição de tipo não é algo com o qual nos sentimos confortáveis.

A proposta do @tinganho foi uma das que falamos também. eu acho que é mais palatável, embora provavelmente seria mais complicado de implementar. Adicionar um novo operador unário ou usar a sintaxe de conversão não é tão elegante quanto apenas usar os nomes dos tipos.

Discutido por um bom tempo no slog hoje. "Aceitar PRs" aqui é "Aceitar PRs assumindo que a implementação não seja muito louca"

A proposta do @tinganho parece muito boa (em relação às outras opções, pelo menos) e gostaríamos de ver uma tentativa de PR que implemente isso.

O complicado é que não queremos ter um caminho de código completamente separado para resolver o tipo de retorno de f(number) vs f(0) , mas o algoritmo de resolução de sobrecarga é totalmente integrado com a suposição de que está trabalhando com um conjunto de _expressions_ em vez de um conjunto de _types_. Mas achamos que com um pouco de truque isso deve ser simples.

O plano básico de ataque seria:

  • Expanda a gramática ao analisar typeof para permitir coisas que se parecem com chamadas de função, acesso a propriedades e acesso a propriedades indexadas
  • Ao analisar os argumentos para uma chamada de função em uma consulta de tipo (vamos chamá-los de _psuedocalls_ / _psuedoarguments_), use a função parseType . Isso criará um TypeNode , mas definirá um sinalizador no nó que indica que era um tipo analisado no contexto de uma consulta de tipo
  • No verificador, checkExpression verifica este sinalizador e chama getTypeFromTypeNode em vez do processamento de expressão normal

@mhegazy , @RyanCavanaugh não tenho certeza de quantos casos de canto a equipe discutiu, então posso trazer alguns aqui para esclarecimento? Eu listei vários exemplos abaixo e comentei cada um com o que eu acho que deveria ser o resultado da operação typeof , com pontos de interrogação em casos questionáveis.


Notação do indexador
var data = [1, 2, 3];
const LOBOUND = 0;
type Elem1 = typeof data[0];        // number
type Elem2 = typeof data[999999];   // number or ERROR?
type Elem3 = typeof data[1+2];      // ERROR or number?
type Elem4 = typeof data[LOBOUND];  // ERROR or number?

var tuple: [number, string] = [123, 'abc'];
type Elem4 = typeof tuple[0];       // number or number|string?
type Elem5 = typeof tuple[1];       // string or number|string?
type Elem6 = typeof tuple[999999];  // number|string or ERROR?

const ABC: 'a-b-c' = 'a-b-c';
let dict = { 'a-b-c': 123, 'd-e-f': true };
type Prop1 = typeof dict['a-b-c'];  // number
type Prop2 = typeof dict['d-e-f'];  // boolean
type Prop3 = typeof dict[ABC];      // ERROR or number or any?

Notação do tipo de retorno da função
// A simple function
declare function f1(n: number): string[];
type Ret1 = typeof f1(number);      // string[]
type Ret2 = typeof f1(0);           // ERROR or string[]?

// An asynchronous function that either accepts a callback or returns a Promise
declare function f2(n: number): Promise<string[]>;
declare function f2(n: number, cb: (err?: any, result?: string[]) => void): void;
type Ret3 = typeof f2(number);                    // Promise<string[]>
type Ret4 = typeof f2(number, any);               // void
type Ret5 = typeof f2(number, Function);          // ERROR: Function not assignable to callback
type Ret6 = typeof f2(number, (err: any, result: string[]) => void); // void
type Ret7 = typeof f2(number, (...args) => any);  // void

// A special function-like object
interface Receiver {
    (data: string[]): void;
    transmogrify(): number[];
}
declare function f3(n: number, receiver: Receiver): Promise<void>;
declare function f3(n: number, callback: (err?: any, result?: string[]) => void): void;
type Ret8 = typeof f3(number, Receiver);           // Promise<void>
type Ret9 = typeof f3(number, any);                // ambiguous? or picks first overload?
type Ret10 = typeof f3(number, Function);          // ERROR
type Ret11 = typeof f3(number, (...args) => any);  // void since not assignable to Receiver

// A function with parameter destructuring
interface CartesianCoordinate {/***/}
interface PolarCoordinate {/***/}
declare function f4({ x: number, y: number }): CartesianCoordinate;
declare function f4({ r: number, t: number }): PolarCoordinate;
type Ret12 = typeof f4(any);        // ambiguous? or picks first overload?
type Ret13 = typeof f4({x;y});      // CartesianCoordinate
type Ret14 = typeof f4({r;t});      // PolarCoordinate
type Ret15 = typeof f4({x;r;t;y});  // ambiguous? or picks first overload?

// Type-ception: is there anything wrong with typeof-in-typeof?
declare function f5(n: number, receiver: Receiver): Promise<void>;
declare function f5(n: number, callback: (err?: any, result?: string[]) => void): void;
function myCallback(err, result) {/***/}
var myReceiver: Receiver;
type Ret16 = typeof f5(number, typeof myReceiver); // Promise<void>
type Ret17 = typeof f5(number, typeof myCallback); // void


Extraindo parte de um tipo de classe/interface

Ou seja, os exemplos typeof (<MyInterface> null).prop1.big.complex; acima.

Deduzo dos comentários acima que isso está fora do escopo e não será suportado. Isso é correto?

Notação do indexador
var data = [1, 2, 3];
const LOBOUND = 0;
type Elem1 = typeof data[0];        // number
type Elem2 = typeof data[999999];   // number
type Elem3 = typeof data[1+2];      // ERROR, only literals allowed here
type Elem4 = typeof data[LOBOUND];  // number when const resolution is done, otherwise any

var tuple: [number, string] = [123, 'abc'];
type Elem4 = typeof tuple[0];       // number
type Elem5 = typeof tuple[1];       // string 
type Elem6 = typeof tuple[999999];  // number|string

const ABC: 'a-b-c' = 'a-b-c';
let dict = { 'a-b-c': 123, 'd-e-f': true };
type Prop1 = typeof dict['a-b-c'];  // number
type Prop2 = typeof dict['d-e-f'];  // boolean
type Prop3 = typeof dict[ABC];      // number when const resolution work is done, otherwise any

Notação do tipo de retorno da função
// A simple function
declare function f1(n: number): string[];
type Ret1 = typeof f1(number);      // string[]
type Ret2 = typeof f1(0);           // error, 0 is not a type

// An asynchronous function that either accepts a callback or returns a Promise
declare function f2(n: number): Promise<string[]>;
declare function f2(n: number, cb: (err?: any, result?: string[]) => void): void;
type Ret3 = typeof f2(number);                    // Promise<string[]>
type Ret4 = typeof f2(number, any);               // void
type Ret5 = typeof f2(number, Function);          // ERROR: Function not assignable to callback
type Ret6 = typeof f2(number, (err: any, result: string[]) => void); // void
type Ret7 = typeof f2(number, (...args) => any);  // void

// A special function-like object
interface Receiver {
    (data: string[]): void;
    transmogrify(): number[];
}
declare function f3(n: number, receiver: Receiver): Promise<void>;
declare function f3(n: number, callback: (err?: any, result?: string[]) => void): void;
type Ret8 = typeof f3(number, Receiver);           // Promise<void>
type Ret9 = typeof f3(number, any);                // picks first overload
type Ret10 = typeof f3(number, Function);          // ERROR
type Ret11 = typeof f3(number, (...args) => any);  // void since not assignable to Receiver

// A function with parameter destructuring
interface CartesianCoordinate {/***/}
interface PolarCoordinate {/***/}
declare function f4({ x: number, y: number }): CartesianCoordinate;
declare function f4({ r: number, t: number }): PolarCoordinate;
type Ret12 = typeof f4(any);        // picks first overload
type Ret13 = typeof f4({x;y});      // CartesianCoordinate
type Ret14 = typeof f4({r;t});      // PolarCoordinate
type Ret15 = typeof f4({x;r;t;y});  // picks first overload

// Type-ception: is there anything wrong with typeof-in-typeof?
declare function f5(n: number, receiver: Receiver): Promise<void>;
declare function f5(n: number, callback: (err?: any, result?: string[]) => void): void;
function myCallback(err, result) {/***/}
var myReceiver: Receiver;
type Ret16 = typeof f5(number, typeof myReceiver); // Promise<void>
type Ret17 = typeof f5(number, typeof myCallback); // void

Estou curioso para saber o que acontece aqui:

const number = "number";
type Ret3 = typeof f2(number); // What happens here?

@SaschaNaz boa pergunta. Uma situação semelhante:

class MyClass { foo; bar; }
declare function f(inst: MyClass): number;
type Ret = typeof f(MyClass);     // number (presumably)

Neste caso faz sentido em typeof f(MyClass) que o MyClass _type_ seja considerado antes do MyClass _value_ (ou seja, a função construtora). O primeiro leva a Ret = number , o último levaria a algo como error: MyClass is not a type .

A mesma lógica se aplicaria a um nome que se referisse a um _type_ e um _const value_? No seu exemplo, isso significaria que o tipo number sempre teria precedência sobre o valor const number . Algum pensamento @RyanCavanaugh?

Certo, resolveríamos isso sob a semântica usual de uma expressão de tipo (como se você tivesse escrito var x: [whatever] ). Então você poderia ter typeof f(MyClass) referindo-se a invocar f com o lado da instância e typeof f(typeof MyClass) referindo-se a invocar f com a função construtora.

Então o exemplo de @SaschaNaz se refere inequivocamente a number como um _type_, não como o _const value_, certo?

const number = "number";
type Ret3 = typeof f2(number); // Promise<string[]>

@RyanCavanaugh você pode confirmar que o terceiro grupo de casos de uso está fora do escopo? por exemplo, do OP:

// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}
}

Este caso de uso (com qualquer sintaxe) não será suportado neste momento, certo?

Eu acho que isso deve ser coberto permitindo expressões this .

   prop2: typeof this.prop1.big.complex;

Acho que a coisa const deve ser resolvida por outro typeof .

type Ret3 = typeof f2(typeof number); // typeof number is string so error here

... enquanto isso bloquearia typeof data[LOBOUND] .

@mhegazy é uma ótima ideia re typeof this . Acabei de perceber que isso já funciona na implementação bifurcada que fiz para esta proposta. Bem, funciona para as aulas. Para interfaces não há erro, mas o tipo this é sempre inferido como any . Saída de corrente do impl bifurcado:

class MyClass {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    };

    prop2: typeof this.prop1.big.complex; // prop2 type is {anonymous: {type: {}}}
}

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    };

    prop2: typeof this.prop1.big.complex; // prop2 type is any
}

É possível inferir this dentro de declarações de interface ou esse recurso permaneceria limitado a classes?

Eu quero fazer dois pontos sobre #6179 e Angular.

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(token: string) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return MyAPI;
}
type MyAPIConstructor = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type
function augmentAPI(api: MyAPIConstructor) {...} // OK
  1. O número de parâmetros pode ser grande. Digamos 15 parâmetros. Ao mesmo tempo, não há sobrecargas, e são apenas as sobrecargas para as quais os parâmetros em typeof são necessários. Então, para este caso, podemos provavelmente pensar em uma sintaxe como a seguir?

    type MyAPI = typeof myAPIFactory(...);
    
  2. A função de fábrica geralmente não é atribuída a uma variável global própria. Uma expressão de função é usada:

    angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { /*...*/ });
    

    É o que geralmente parece. Como pode ser visto, typeof não pode ser usado aqui.

@thron0 minha intuição é que algo que tem mais do que alguns parâmetros, mas _também_ retorna um tipo anônimo, será muito raro. O que você acha?

Vai se tornar raro quando o Angular 1 se tornar raro, não antes.

Basicamente, o que você descreveu é o que é uma _fábrica_ típica do Angular:

uma coisa que tem mais do que alguns parâmetros, mas também retorna um tipo anônimo

É uma função que recebe dependências como seus parâmetros e cria uma instância de um _service_. Você pode pensar que um grande número de parâmetros pode ser um incômodo, mas o fato é que essas fábricas nunca são chamadas diretamente. O contêiner DI os chama. A fábrica é chamada quando algo requer seu valor de retorno (o serviço) como uma dependência. E é chamado apenas uma vez, pois os serviços são sempre singletons em Angular. No entanto, um serviço pode ser qualquer coisa, portanto, se precisarmos de um comportamento não singleton, a fábrica pode retornar um construtor (ou uma função de fábrica). Assim como neste exemplo de código:

angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(token: string) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return MyAPI;
});

É assim que minha solução atual para essa situação se parece:

class MyAPI {
    // dependencies (1)
    protected $http: HttpSvc;
    protected id: number;

    constructor(token: string) {...}
    foo() {...}
    bar() {...}
    // Static properties cannot be used with this approach because:
    // 1) this class is a global variable so it can be shared by different 
    // instances of the DI container,
    // 2) the id property isn't available here as it's initialized in the subclass
    //// static id0 = id;
}

angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { // (2)
    return class extends MyAPI {
        $http = $http; // (3)
        id = id;
    };
});

// this type is needed for type annotations in the services that require MyAPI as a dependency
type MyAPIConstructor = typeof MyAPI;

angular.module('app').factory('someOtherService', function(MyAPI: MyAPIConstructor) {
    var api = new MyAPI('qwerty');
    /* ... */
});

Como você pode ver, é totalmente feio e doloroso. Eu tenho que listar as dependências três vezes. Se eu tiver mais dependências, às vezes é mais simples escrever a classe da maneira usual (dentro da função de fábrica) e declarar uma interface para seus consumidores.

Oi, eu quero propor deixar a sintaxe/semântica typeof intacta e, em vez disso, implementar uma álgebra de acesso de tipo simples:

Vamos imaginar a extensão de sintaxe de tipo:

type          ::=  ... |  literalType | type typeAccess | guarded
guarded    ::= "type" id
literalType   ::= stringLiteral | numberLiteral | symLiteral | booleanLiteral 
typeAccess    ::= typeField | typeSubscript | typeCall
typeField     ::= "." id
typeSubscript ::= "[" type "]"
typeCall      ::= "(" [type {"," type}] ")"

Cada identificador no escopo pode ser vinculado simultaneamente às duas entidades:

  • tipo de tempo de compilação
  • valor do tempo de execução

o tipo guard extrai apenas o primeiro. portanto

class A {}
var a: A;

é equivalente a

class A {}
var a: type A;

neste caso, se tivéssemos uma classe

class ABC {
    a: number;
    b(x: number): string;
    c:string[];
    d:[number, string];
}

então poderíamos escrever

var a: (type ABC).a; // number
var b: (type ABC).b(number); // string
var c: (type ABC).c[number]; // string
var d: (type ABC).c[0]; // number

Ele também abrange a mesclagem de entidades _type_ e entidades _value_ sob o mesmo identificador no escopo léxico.

interface SAME {
    x: number;
}
namespace SAME: {
    export type x = boolean;
    export var x: string;
}

var a: SAME.x   // boolean
var b: (type SAME).x  // number
var b: (typeof SAME).x  // string

@RyanCavanaugh , @sandersn você pode examinar essa ideia?

Como você captura o tipo de expressão com sua proposta?

Em qui, 19 de maio de 2016 12:26 Anatoly Ressin, [email protected] escreveu:

Oi, eu quero propor deixar o typeof sintaxe/semântica intacto e
em vez disso, implemente uma álgebra de acesso de tipo simples:

Vamos imaginar a extensão de sintaxe de tipo:

digite ::= ... | literalType | tipo tipoAcesso | guardado
protegido ::= "tipo" id
literalType ::= stringLiteral | númeroLiteral | simLiteral | booleanLiteral
typeAccess ::= typeField | tipoSubscrito | typeCall
typeField ::= "." identificação
typeSubscript ::= "[" tipo "]"
typeCall ::= "(" [type {"," type}] ")"

Cada identificador no escopo pode ser vinculado simultaneamente aos dois
entidades:

  • tipo de tempo de compilação
  • valor do tempo de execução

o guarda _type_ extrai apenas o primeiro. portanto

classe A {}
a: A

é equivalente a

classe A {}
um: tipo A

neste caso, se tivéssemos uma classe

classe ABC {
um número;
b(x: número): string;
c:string[];
d:[número, sequência];
}

então poderíamos escrever

var a: (tipo ABC).a; // numbervar b: (digite ABC).b(number); // stringvar c: (digite ABC).c[number]; // stringvar d: (tipo ABC).c[0]; // número

Ele também abrange a fusão de entidades _type_ e entidades _value_ sob o
mesmo identificador no escopo lexical.

interface MESMO {
x: número;
}namespace MESMO: {
tipo de exportação x = booleano;
export var x: string;
}
var a: SAME.x // booleanvar b: (type SAME).x // numbervar b: (typeof SAME).x // string

@RyanCavanaugh https://github.com/RyanCavanaugh , @sandersn
https://github.com/sandersn você pode examinar essa ideia?


Você está recebendo isso porque está inscrito neste tópico.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/6606#issuecomment -220378019

Seu exemplo me lembra que um tipo de expressão pontilhada e tipo de propriedade de tipo #1295 é uma proposta bastante semelhante. Exceto que um é mais estático o outro mais dinâmico.

Eu gosto da ideia de pular typeof palavra-chave também.

   prop2: this.prop1.big.complex;

@mhegazy no seu exemplo você está usando typeof em um this-expression :

prop2: typeof this.prop1.big.complex;

Já que podemos fazer isso hoje:

someProp: this;

Na minha opinião, uma extrapolação da sintaxe acima para propriedades mais pontilhadas é:

someProp: this.prop1.prop2.prop3;

E não:

someProp: typeof this.prop1.prop2.prop3;

E para continuar com a extrapolação - por que não pular typeof todos juntos?

someProps: foo(number)

por que não pular typeof todos juntos?

Uma razão em que posso pensar é que se tornaria ambíguo se você está fazendo referência ao lado da instância de uma classe ou ao lado estático:

class C {}

// Does 'x' have the instance type of 'C',
// or does it have the shape of its constructor?
let x: C;

Eu gostaria de fazer referência ao tipo de uma propriedade em uma interface dentro de outra interface também. Postei meu exemplo como #10588, que agora está fechado em favor deste ticket.

Seria bom escrever algo assim:

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<Foo.bar>; // OR: bar: Box<typeof Foo.bar>;
}

o que se traduziria apenas nisso:

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<string>;
}

Bem, alguns dos cenários mencionados anteriormente podem agora ser expressos com _tipos de acesso indexados_ (#11929). Basta combinar esse recurso com a expressão padrão typeof e você pode obter algo assim:

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: MyInterface["prop1"]["big"]["complex"];
}

e funciona! (atualmente em typescript@next)

Eu acho que a próxima coisa natural seria ter algo semelhante, mas para funções, alguns "tipos de chamada de função" como este:

interface FunctionLike{
    (arg1 : number, arg2 : string) : {a : number; b : string;}
}

type ReturnType = FunctionLike(number, string); // {a : number; b : string;} 

para que possamos combiná-lo naturalmente com o operador typeof

interface BigThing {
    testString: string;
    testNumber: number;
    testBoolean: boolean;
}

function getSmallThing(thing:BigThing){
    return {
        testString : thing.testString,
        testBoolean : thing.testBoolean
    }
}

type SmallThing = typeof getSmallThing(BigThing) // {testString: string; testBoolean : boolean}

E parece que esta é a sintaxe que já foi proposta no início deste tópico (@tinganho). :)
Mas seria um recurso mais geral que comporia muito bem com o operador typeof atual, assim como os _tipos de acesso indexados_ fazem agora.

No entanto, algumas perguntas/dúvidas ainda permanecem:

  • Esse recurso lidará com alguns outros cenários como os _tipos de acesso indexados_? Se não, vale a pena implementá-lo?
  • Eu suponho que na maioria dos casos seria bom não precisar especificar os tipos de argumentos, já que o cenário mais comum é "Eu só quero obter o tipo desta função que não tem outras sobrecargas". novo operador específico para isso seria bom (por exemplo typeofreturn ou returnof )

Parece que isso é tecnicamente viável. Pode exigir a refatoração de resolução de sobrecarga/inferência de argumento de tipo para que você não precise resolver os argumentos. Eu não acho que os genéricos representam um problema adicional. Uma pergunta a fazer é o que acontece se nenhuma sobrecarga corresponder à chamada.

Eu acho que não é óbvio se esse recurso é desejável. Se uma função retorna um tipo anônimo muito elaborado, não seria mais útil para o autor nomear o tipo, para que ele não precise ser referenciado dessa maneira? Admito que isso se baseia apenas em uma preferência estilística. Suponho que não tenho certeza de quão conveniente isso será para usar na prática. Eu acho que é bom para o acesso ao elemento.

tipo P = tipo de foo(0); // P é qualquer[]
tipo Q = tipo de foo(true); // Q é string

Com esta discussão sobre permitir que os parâmetros sejam referidos por seus tipos, sugiro permitir o mesmo para as funções reais também.
Contexto: Eu gostaria de digitar uma função para map sobre objetos:
function map<T, F extends Function>(fn: F, obj: T): { [P in keyof T]: typeof F(T[P]) }
A razão pela qual eu gostaria de poder me referir à função por seu tipo F (além de apenas seu nome fn ) seria para casos em que seu nome pode estar indisponível, por exemplo, I' gostaria de poder dizer type MapFn<T, F extends Function> = { [P in keyof T]: typeof F(T[P]) } .

Algo que provavelmente seria muito mais fácil de implementar (e ainda muito útil) seria:

const myFunc = () => ({ x: 10 });
type myType = returnof myFunc;    // { x: number; }

Em teoria, eles também podem ser encadeados se você quiser obter o tipo alguns níveis de profundidade. Alguma ideia?

Edit: Acabei de perceber que isso foi mencionado acima por @mpawelski 😄

@dehli Isso não funcionaria para funções sobrecarregadas. Ou funções genéricas onde um parâmetro de tipo é usado no tipo de retorno.

@JsonFreeman As funções sobrecarregadas não poderiam apenas OR os vários tipos de retorno? E funções genéricas podem exigir que você especifique o tipo?

Seria possível, mas não tenho certeza de quão útil isso seria. Parece que as pessoas querem algo mais sofisticado.

Eu realmente espero que isso seja implementado em breve, pois é uma dor real.

Gambiarra

Como a última solução alternativa não funciona mais, atualmente uso isso:

// 0 argument function
export default function returnof<T>(fn: () => T): T;

// 1 argument function
// If no ambiguity simply infer the return type
export default function returnof<A, T>(fn: (a: A) => T): T;

// 1 argument function, with possible overload for the argument type.
// Explicitly set the type and use the correct overload
export default function returnof<A, T>(fn: (a: A) => T, a: A): T;

// ...

Você não precisa especificar argumentos se não houver sobrecarga de sua função.

const hello = (arg: number) => 'World'

const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // string

Funções sobrecarregadas

declare function hello(): void;
declare function hello(a: number): number;

const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // void

const helloReturnValue = returnof(hello, 42)
type helloReturnType = typeof helloReturnValue // number

Todas as definições estão aqui:
https://github.com/kube/returnof/blob/master/lib/returnof.d.ts

Acabei de criar um pequeno pacote NPM para agrupá-lo facilmente sem poluir seu projeto.

Se os Generics Opcionais forem adicionados como proposto em https://github.com/Microsoft/TypeScript/issues/2175 , isso permitirá uma solução simples somente declarativa:

type Return<T extends () => S, S> = S

E use assim:

type helloReturnType = Return<typeof hello>

@mhegazy @JsonFreeman Algum plano para esse recurso?

Olá a todos,
Eu tenho uma solução alternativa possível para este problema (desculpe se já foi sugerido e eu perdi) - eu propus na edição # 13949. Eu não sabia sobre o operador typeof nesse estágio, mas acho que minha solução é mais geral. Basicamente, introduza uma nova sintaxe algo como =MyType , que pode ser usada onde quer que você use MyType , mas em vez de declarar que o objeto é do tipo MyType , ele cria um tipo chamado MyType do tipo inferido do objeto. Por exemplo, é assim que eu o usaria na criação de um componente Vue.

function createData(){
  return <=MyData>{
    dataProp1: <string>null
  }
}

function method1(){
  let self: MyComponent = this;
  console.log(self.dataProp1);
  self.method2();
}

function method2(){
  let self: MyComponent = this;
  //dostuff
}

type MyComponent = MyData & MyMethods;

let componentOptions = {
  data: createData,
  methods: <=MyMethods>{method1, method2}
}
//todo: register component...

Observe que a função createData também pode ser escrita

function createData(): =MyData {
  return {
    dataProp1: <string>null
  }
}

Eu encontrei um trabalho muito divertido que generaliza para qualquer expressão:

const varWithRightType = (false as true) && some.deep.access()
type MyType = typeof varWithRightType;

EDIT: PARE DE RIR DE MIM ISSO É SÉRIO DIGITAÇÃO

@johnfn isso parece divertido para casos simples :), mas se sua função exigir alguns parâmetros, você precisará fazer um trabalho adicional como este:

const stateProps = (false as true) && mapStateToProps({} as any);

Estou usando uma solução diferente, que é especialmente útil para React & Redux, ao tentar usar a inferência de tipos para obter o tipo de props da função mapStateToProps . Então não há mais necessidade de declarar e manter manualmente a interface de Props injetada pelo Redux connect que é tedioso de manter e propenso a erros.
Essa solução alternativa também lidará bem com qualquer assinatura de função para outros casos de uso.

Exemplo de caso de uso "React & Redux" do operador typeof :

O exemplo abaixo tenta mostrar um caso de uso comum de projeto real para uso do operador typeof para derivar um tipo de uma declaração de função que seria extremamente útil se adicionado ao TypeScript.

import { returntypeof } from 'react-redux-typescript';
import { RootState } from '../../store';
...

const mapStateToProps = (state: RootState) => ({
  currencies: CurrencyRatesSelectors.getCurrencies(state),
  currencyConverter: storeState.currencyConverter,
});

const stateProps = returntypeof(mapStateToProps); 
type Props = typeof stateProps & typeof dispatchToProps;
type State = {};

class CurrencyConverterContainer extends React.Component<Props, State> {
...

Fonte: https://github.com/piotrwitek/react-redux-typescript-patterns#react -connected-components

A solução do @kube e seu pacote https://github.com/kube/returnof parecem funcionar conforme o esperado! 👍

Hum. Eu gostaria que returnof ajudasse a digitar map() baseado em tupla de um arquivo .d.ts , mas dada sua dependência da linguagem de expressão ( const não utilizável em contextos de ambiente ), temo que meu caso de uso com isso seja mais complicado, pelo menos.

Edit: parece que os genéricos opcionais estão mesclados agora, o que significa que a versão declarativa da linguagem de tipo declarativa do @kube ( type Return<T extends () => S, S> = S -> type helloReturnType = Return<typeof hello> ) se tornaria viável. :D

Correção: não, o suporte mesclado para valores genéricos padrão não parece permitir especificar parte dos genéricos enquanto permite que outros retornem aos seus valores inferidos; Return<typeof hello> apenas gera o erro Generic type 'Return' requires 2 type argument(s). .

@tycho01 Sim, fiquei desapontado por não resolver o problema.

Já abri um problema relacionado sobre a inferência de tipos genéricos opcionais :

14400

Geralmente, você pode obter o tipo de retorno de uma função por meio de uma combinação de uma função de ambiente fictícia e inferência de tipo:

ambiente function returnTypeOf<RT>(fn:(...rest:any[])=>RT):RT {return void 0};
localmente var r = returnTypeOf(someFunction); obtém valor undefined

Mas se você quiser reutilizar esse tipo de retorno, você deve capturá-lo ... então estamos de volta onde começamos:

type RT = typeof r;

Seria muito mais fácil se estendermos inicialmente o conceito de typeof para permitir returntypeof ou ainda melhor inferir esse uso de typeof fn(a,b,c,...) , que poderia capturar diferentes tipos de retorno de assinatura . Essa compreensão do tipo de retorno já é realizada pelo TS internamente.

typeofexpression () seria um tipo de recursão de tipo de retorno misturada com operações de tipo: por exemplo

type E = typeofexpression (f(1) + g("x"))

é

type E = typecomprehensionof (typeof f(1) + typeof g("x"))

que pode ser compreendido como um dos

type E = typecomprehensionof (string + string) ou seja string
type E = typecomprehensionof (string + number) ou seja string
type E = typecomprehensionof (number + number) ou seja number

Quão difícil isso é internamente e os custos de desempenho são desconhecidos para mim.

EDITAR -------------------------------------------------- ------------

Eu queria adicionar ... isso é especialmente importante para qualquer pessoa que precise usar Function.bind, Function.apply, Function.call porque eles atualmente retornam o tipo any e isso significa que eles precisam ser constantemente type- anotados para garantir que eles não saiam do processo de verificação de tipo.

Poder referenciar o tipo de retorno do argumento da função seria... bliss...

@poseidonCore

Poder referenciar o tipo de retorno do argumento da função seria... bliss...

Eu gosto mais do typeof <expression> atual, e o problema do tipo de retorno já é abordado em #12342.

Eu estava apenas usando typeofexpression e typecomprehensionof como meio de diferenciar esses usos na explicação. Eu preferiria typeof (expression) como a sintaxe real.

Meu ponto era que compreender o tipo de uma expressão exigiria compreender o tipo de retorno de uma função... f(1) também é uma expressão. #12342 está relacionado desta forma. Eles não são mutuamente exclusivos.

Já podemos fazer typeof para variáveis ​​e como uma expressão é uma operação sobre variáveis ​​e funções, o próximo requisito é poder retornar o tipo de uma função ... e então compreender o resultado com base nas regras de tipo .

Na verdade, bom ponto. O problema #12342 quer é uma maneira de acessar o tipo de retorno como uma coisa semelhante a uma propriedade para tipos genéricos, então não entendi a relação.

Que tal usar expressões para a chamada da função typeof , como na proposta, mas usando tipos diretamente no lugar de parâmetros?

Por exemplo

function myFunction<T>(param1: T, param2: string, param3: number): T & {test: string} {
  // ...
}

type ResultType = typeof myFunction({hello: string}, string, number)
// ResultType is: {hello: string} & {test: string}

Observe que isso não impede ninguém de usar tipos de variáveis ​​com escopo local, usando typeof nas chamadas, ou seja:

type ResultType = typeof myFunction(typeof obj, string, number)
// ResultType is: typeof obj & {test: string} 

Isso parece um pouco melhor do que a proposta original, pois funciona em um contexto ambiente e parece mais flexível em geral. Para mim, também deixa mais claro que não estamos realmente chamando a função, apenas tentando retornar o tipo de seu valor de retorno.

Como eu fiz isso para o meu caso simples:

interface IAlertMessage {
  addedAt: number;
  text: string;
  type: "error" | "warning" | "info" | "success";
}

declare let alertMessageInterface: IAlertMessage;

const messages: IAlertMessage[] = [];

function addMessage(text: string, type: typeof alertMessageInterface.type): void {
  messages.push({addedAt: new Date().getTime(), text, type});
}

addMessage("something", "info"); // <- OK - and also has intellisense for the second parameter (after typing first " of the second parameter, press Ctrl+Space in VSCode)
addMessage("something", "fatal"); // <- Compilation error : error TS2345: Argument of type '"fatal"' is not assignable to parameter of type '"error" | "warning" | "info" | "success"'.

O acima também funciona com membros da própria interface:

declare let myIface: MyInterface;

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },
    prop2: typeof myIface.prop1.big.complex.anonymous;
}

A vantagem de usar declare let ... em vez de type ... para mim é que não produz nada no arquivo .js de saída.

@varadero isso não resolve o problema em questão, que é recuperar o tipo de retorno de uma chamada de função. Tudo o que você está fazendo é recuperar o tipo de uma propriedade de um tipo, que é um recurso suportado desde o TypeScript 2.2, acredito.

Na verdade, esse é o tipo de propriedade de um valor (falso). O compilador não permite que você use typeof com tipo ou interface "pura", ele só funciona em valores.

Isso seria ótimo ter!

já que type KEYOF<T extends any[]> = keyof T[0] já existe, typeof T[0]('something') funcionaria bem com esta implementação, assumindo function myFunc<T, K extends KEYOF<T>>(type: K)>{ } e myFunc([(r: string) => r]) (retornaria string para typeof T[0]('something') ) ?

Isso seria poderoso junto com o polimórfico this .

Hora de isso acontecer!

Relendo este tópico, tentei entender o que estamos fazendo com typeof em termos de sintaxe e por quê.

Evidentemente, no caso trivial typeof foo já implementado, dá o tipo de foo . (Imagino que pet is Fish seja imune ao uso de sintaxe especial.)

Na minha leitura atual do que essa palavra-chave deve fazer na proposta atual, a palavra-chave em si não faz mais do que faz agora, e a proposta atual, na verdade, não está relacionada à palavra-chave typeof .

Eu trago isso porque é importante no caso que mencionei acima, onde estamos aplicando uma função armazenada como um tipo (seja através de type ou como um genérico), em vez de um valor.

Dado isso, presumo que, enquanto typeof fn<A>(string) precisa typeof para levantar a variável de nível de expressão fn para uso no nível de tipo, Fn<A>(string) por outro lado , com Fn como um genérico contendo uma função, não exigiria isso e, portanto, poderia ser 'aplicado' para obter seu tipo de retorno apropriado aqui sem a necessidade de typeof .

Nesta interpretação, verificaríamos as chamadas de função após qualquer tipo de função potencial: ao lado typeof fn(...) / typeof fn<...>(...) também Fn(...) / Fn<...>(...) , se não for função literal ((foo: Foo) => Bar)(Baz) / + genéricos. Caso contrário, o plano de ataque deve permanecer intacto.

Talvez eu esteja interpretando mal como vocês veem isso, talvez funções armazenadas em tipos simplesmente não tenham sido consideradas (já que não encontrei menção a elas). De qualquer forma, achei que valeria a pena confirmar.

Se anunciar o aplicativo de função se tornar mais uma sobrecarga semântica para typeof , além de desambiguar construtores de classe e elevar variáveis ​​de nível de expressão para o nível de tipo (além do nível de expressão typeof ), as coisas parecem gradualmente mais complicados, como já indicado por algumas perguntas anteriores neste tópico.

Edit: um tipo genérico já pode retornar um tipo de função, que também pode ter genéricos. Isso significa que outra permutação seria GetFn<A><B>() , com o primeiro conjunto genérico pertencente à invocação de tipo genérico, o último pertencente à invocação de função. Não GetFn<A><B><C>() , embora GetFn<A><B>()<C>() também seja legítimo. A conclusão anterior permanece inalterada: qualquer tipo pode conter uma função e, portanto (potencialmente) ser aplicado como uma função.

Edit 2: Acabei de perceber que haveria uma ambiguidade infeliz em X<Y>() . Agora, X seria um tipo que retorna um tipo de função.

  • Se X não for genérico, isso fica claro -- <Y> pertence à chamada da função.
  • Se X for genérico e requer o parâmetro, isso também fica claro -- ele pertence ao tipo e a função não recebe nenhum parâmetro de tipo.
  • No entanto, se X tiver um genérico opcional, isso se tornará ambíguo.

    • Em uma interpretação, X recebe um parâmetro de tipo, enquanto a função não.

    • Em outra interpretação, X é deixado para usar seu parâmetro de tipo padrão, enquanto <Y> parametrizar a chamada da função.

Ainda não tenho certeza qual seria a melhor solução aqui.

  • Uma opção seria forçar as invocações de tipo confiando totalmente nos parâmetros de tipo padrão para usar explicitamente o <> vazio em vez de permitir também pular isso. No entanto, esta seria uma mudança de ruptura.
  • Uma alternativa seria impor tal restrição na invocação de função de nível de tipo - como isso seria novo, nenhuma alteração importante seria introduzida. No entanto, essa abordagem seria deselegante, pois introduziria uma assimetria entre a sintaxe para invocação de função entre a expressão e as linguagens de tipo, o que pode torná-la menos intuitiva.

@icholy : sim, essas edições estão apenas aumentando seu ponto, eu sei.

Edit 3: ok, esse recurso agora supera o topo da minha lista de desejos. Nenhuma outra atualização chega nem remotamente perto em termos de impacto para inferência.

wat

@icholy : em resumo, se a 'função' estiver em um genérico/tipo, ainda escreveríamos typeof ?

Se isso for feito para interagir com sobrecargas corretamente, ele realmente fornece funções "variádicas" de graça (elas são apenas curry em vez de ter > 1 aridade), pois você pode definir recursivamente o tipo de retorno de uma função em termos do aplicação de uma de suas sobrecargas, com alguma sobrecarga de caso-base. É assim que as coisas são feitas em Haskell, e seria um recurso maravilhoso ter no TypeScript.

@masaeedu : isso parece interessante. E sim, sobrecargas são definitivamente o que torna esta proposta tão interessante -- elas podem ser usadas para combinar padrões em diferentes opções, incluindo any fallbacks. Verificações de tipo como essa até agora não eram possíveis no nível de tipo ainda.

Admito que usei mais Ramda do que Haskell. Mas a partir desse pano de fundo, pensei que curry normalmente não combinava bem com funções variádicas, pois o curry precisaria 'saber' se deve retornar o resultado ou outra função para lidar com argumentos adicionais.

Você poderia mostrar algum pseudo-código sobre como você veria essa idéia funcionar para funções variádicas como Object.assign (pulando detalhes como & vs. Overwrite como eu usei no meu PoC por R.mergeAll )?

@tycho01 Eu tenho brincado com código assim:

interface Pop<TVarStack extends VarStack> {
    (a: TVarStack["head"]): Pop<TVarStack["tail"]>
}

interface VarStack<THead = any, TTail extends (void | VarStack) = any> {
    head: THead
    tail: TTail
    <TNew>(a: TNew): VarStack<TNew, this>
    (): (a: Pop<this>) => any
}

// Figure out implementation later :)
let stack: VarStack<void, void>;
const pop = stack(new Date())(10)("Bob's")("your")("uncle")()

// a, b, c, d, and e are well-typed
pop(a => b => c => d => e => {
    // Need this because can't figure out how to encode base-case in Pop recursion
    return stack
})

VarStack é um tipo de função "variável" em certo sentido, pois você pode fornecer um número arbitrário de argumentos heterogêneos e registrará fielmente as informações do tipo associado usando recursão no nível do tipo. Infelizmente, o TypeScript não tem um bom suporte para transformação e correspondência de padrões no nível de tipo, da mesma forma que Haskell.

Se tivéssemos acesso a typeof , eu seria capaz de resolver o problema do caso base para Pop , já que returnof(overloadedFunction(T)) essencialmente me daria uma maneira de fazer o padrão- correspondência no nível do tipo.

@tycho01 Eu não posso fazer isso precisamente para Object.assign , porque como eu disse, isso só funciona para funções curry, mas vou tentar criar uma função curried assign para você que funcione aproximadamente o mesmo caminho.

Eu gostaria que eles generalizassem um pouco a álgebra de tipos, de modo que, por exemplo (a: string) => (b: number) => void fosse apenas açúcar para Func<string, Func<number, void>> , e (a: string, b: number) => void fosse apenas açúcar para Arity<number, Arity<string, void>> , ou algo parecido. Então, precisaríamos apenas de ferramentas de propósito geral para transformar tipos para obter muitos recursos interessantes de forma emergente.

Peço desculpas a todos pelo post triplo. @tycho01 Aqui está curry, "variádico" assign :

interface Assign<TCurr extends {} = {}> {
    <TAdd extends {}>(a: TAdd): Assign<TCurr & TAdd>
    (): TCurr
}

let assign: Assign = undefined // implement somehow
const result = assign({ foo: "bar" })({ baz: 42 })()

@masaeedu : Hah, então acho que é como ter uma função redutora :), embora essa parte possa ficar mais difícil se houvesse argumentos não variádicos (?) também.

Eu gosto da ideia; Eu definitivamente não tenho pensado tanto na direção de interfaces.
Se pudermos alterar o JS para as tipagens, talvez eu peça ao TC39 para apenas alterar Object.assign para um mergeAll não variádico. :D

Não como minha iteração baseada em incrementos como essa realmente funcionou com funções na prática até agora (#17086)...

@ tycho01 Acho improvável que você obtenha muita tração se solicitar que funções variáveis ​​sejam transformadas em funções de aridade fixas, pois há um custo de tempo de execução para funções curry ou aplicativos repetidos que JS (ou pelo menos mecanismos e códigos JS existentes) provavelmente não é altamente otimizado para. Acho que o que precisamos é que o TypeScript torne igualmente fácil construir e desconstruir tipos para funções arity > 1 recursivamente. Eu acho que tudo isso volta para # 5453.

Outros podem achar interessante saber que o c++ tem um recurso muito semelhante: decltype(expression)

Esse recurso chegará ao TypeScript?
Prefiro muito a forma proposta original onde o argumento é uma expressão, mas a intenção da equipe TS de ter uma sintaxe especial com tipos em posições de expressão apenas complica tudo e atrasa o pouso na linguagem.
Precisamos manter esse tópico.

Eu ficaria muito feliz se pudéssemos implementar um subconjunto desta proposta. Ou seja, este único:
type A = typeof anotherVariable . Eu poderia fazer tudo com apenas uma linha. interface B extends A , <B & A> : B & A , etc.

Estou vendo muitos casos de uso para esse tipo de coisa com o React. Quando você cria um componente de ordem superior, é muito mais difícil sugerir à declaração de classe do componente que ele é atualmente um HOC.

O que acabei fazendo foi criar duas classes. Uma classe estende React.Component e adiciona todos os métodos adicionais, enquanto a classe dentro da função HOC manipula o render() .

@blindbox type A = typeof anotherVariable já funciona:

var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));

function checkItem(item: typeof data) {
    // item is Array<{ raw: number; square: number; }>
}

@Igorbek Uau, você está certo.

Ok, eu descobri porque não funcionou no meu fim.
Isso não vai funcionar

interface B {
  thisIsB: boolean
}

const typeTest = (type: any) => {
  return 1 as any as App & B
}
type A = typeof typeTest(1)
declare const aVal: A; // aVal's type isn't of type App & B, but something else.

Isso, no entanto, funcionará.

interface B {
  thisIsB: boolean
}

const typeTest = (type: any) => {
  return 1 as any as App & B
}
const typeTestVal = typeTest(1)
type A = typeof typeTestVal
declare const aVal: A; // aVal's type is of type App & B!

Sim, então o problema é que ele não pode ser usado em determinados casos de uso, como quando você precisa usar argumentos de tipo genérico.

function someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item: T) { ... }

// it is impossible to work this around because there's no chance to create a fake variable that would be parameterized by T
function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }

@Igorbek :

Prefiro muito a forma proposta original onde o argumento é uma expressão, mas a intenção da equipe TS de ter uma sintaxe especial com tipos em posições de expressão apenas complica tudo e atrasa o pouso na linguagem.

Então, desde que eles escreveram isso, obtivemos literais para strings e números, o que significa que as coisas estão ficando um pouco mais confusas - elas funcionariam de qualquer maneira. A matriz literal e os objetos parecem semelhantes às suas notações de tipo, até agora tudo bem.

Então também temos valores/tipos armazenados, por exemplo, variáveis, genéricos, outros tipos. Para que as coisas sejam úteis, não devemos nos contentar com parte disso aqui.
O que é um bom argumento para sua abordagem baseada em tipo aqui é que permitir essa mistura vem de graça no nível de tipo. Ou seja, no nível do tipo, já poderíamos dizer typeof myVar , o que significa que se esta função 'aplicativo' for adicionada ao nível do tipo, poderemos automaticamente conectar os tipos armazenados e as variáveis ​​regulares .

Eu estaria interessado em ver seus pensamentos adicionais sobre a abordagem originalmente sugerida. É certo que isso pode servir para expor um pouco mais ao nível de tipo: operadores baseados em JS (pense ! && || + - * / instanceof ), bem como operadores específicos do TypeScript (operador de asserção ! ).
A coisa sobre esses operadores JS é como ... eles são bastante inúteis no nível de tipo como está, pois permitir que eles operem em literais para produzir os tipos de resultados literais correspondentes é atualmente considerado fora do escopo ( ref ) -- expressão -level 1 + 1 apenas produz o tipo number , e semelhante para os outros.
Com isso em mente, eu meio que fiquei com a proposta baseada em tipos deles.

Esse recurso chegará ao TypeScript? [...] Precisamos manter esse tópico em dia.

Sugeri esta proposta como uma solução mais geral para um sintoma menor aqui , embora com sucesso limitado.

@tycho01 Meus argumentos para variação baseada em expressão de typeof são quase os mesmos declarados originalmente por @yortus :

  • consistência : typeof existente (na posição do tipo) já está trabalhando com expressões, porém restrito a aceitar um único símbolo. Assim, aceitar tipos ou pseudo-chamadas introduziria uma sintaxe muito mais complicada que é muito mais uma ramificação de sintaxe (além de expressões e tipos).

    • os usuários não precisam aprender nova sintaxe

  • simplicidade : o TypeScript parece já ter todas as facilidades necessárias para implementar esse recurso de maneira discreta (foi comprovado pelo PR)
  • completeness : as expressões têm pelo menos a mesma expressividade que os tipos, pois sempre podemos usar asserções de tipo ( (undefined as any as <any arbitrary type can be here>) ), mas às vezes o sistema de tipos carece de tipos que podem ser expressos em expressões (os tipos spread e rest foram introduzidos depois as expressões correspondentes foram desembarcadas).

Obrigado @tycho01 que você trouxe esse ponto para promised PR (na verdade, eu vim aqui depois de seus comentários lá), isso realmente mostra como um recurso tão simples e genérico pode cobrir cenários muito mais complexos de uma maneira muito elegante sem assar em um comportamento muito específico para a língua.

Eu vejo o typeof estendido como um verdadeiro divisor de águas para a expressividade do sistema de tipos, mais como tipos mapeados / keyof fez isso.

@Igorbek : Obrigado por elaborar, eu vejo de onde você está vindo então. Talvez essas duas abordagens sirvam a casos de uso diferentes.

Acho que você está demonstrando melhor o valor de hoje da proposta original do que o post original hoje, pois o TS atual (ou, bem, o 2.3.3 do Playground) pode fazer muitos dos cenários originais já funcionarem:

// I have a strongly-typed collection but the element type is anonymous/unknown, how can I reference the element type? (#3749)

// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
declare function checkItem(item: typeof data[0]): any
// ^ no longer errors, needs no further change

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42 };
type Thing2Type = typeof things['thing-2'];
// ^ no longer errors, needs no further change

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0);
// ^ the `.item` access works, but function applications still errors, would be fixed by either version of the proposal.

// A function returns a local/anonymous/inaccessible type, how can I reference this return type? (#4233, #6179, #6239)

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
  class MyAPI {
    constructor(http) {}
    foo() {}
    bar() {}
    static id = id;
  }
  return new MyAPI($http);
}
declare function augmentAPI(api: typeof myAPIFactory(HttpSvc, number) /* ERROR */): any
// ^ function applications errors, would be fixed by either version of the proposal, if using slightly different syntax.

// I have an interface with a complex anonymous shape, how can I refer to the types of its properties and sub- properties ? (#4555, #4640)

// Declare an interface DRY-ly and without introducing extra type names
type MyInterface = {
  prop1: {
    big: {
      complex: {
        anonymous: { type: {} }
      }
    }
  },
  // prop2 shares some structure with prop1
  prop2: MyInterface['prop1']['big']['complex'];
}
// ^ no longer errors after swapping `.k` access to `['k']`. `typeof` was unnecessary and counter-productive -- MyInterface isn't exposed on the expression level.

Acho que esses exemplos não estão mais defendendo a expressão ou as abordagens de tipo - a maioria é obsoleta e o aplicativo de função trivial seria ativado por qualquer um.

Reconhecidamente aplicação de função baseada em nível de tipo, conforme focado por tinganho, a equipe TS e eu podemos expor variáveis ​​de expressão por meio de typeof , embora, como você observou, o nível de tipo reconhecidamente tende a ficar atrás da funcionalidade exposta na expressão nível. Este aplicativo de função em si, bem como a sintaxe de propagação que você mencionou, são obviamente exemplos primários, e eu definitivamente adoraria vê-los abordados. Talvez grande parte da lacuna atual possa ser resolvida aqui.

Da mesma forma, uma abordagem de expressão em primeiro lugar também permitiria injetar tipos usando <MyType> whatever ou whatever as MyType , embora, como typeof na abordagem de tipo, parecesse uma reflexão tardia: perguntas sobre a aplicação de funções armazenadas em tipos/genéricos permanecem, o que provavelmente compensa a maior parte do valor agregado real dessa abordagem baseada em tipo (embora não seja mencionada lá) - inferência precisa para funções de ordem superior, bem como condicionais baseadas em tipo como naquele tópico promised .* Pior ainda, ao contrário do caso de uso do OP, eles não têm soluções alternativas.

Acho que veja onde as ideias se chocam -- as propostas atuais têm visões contraditórias sobre se a palavra-chave typeof mudaria sua expressão para o nível de valor versus (na minha interpretação) deixar typeof como está , mas expondo a sintaxe do aplicativo de função no nível do tipo.

A meu ver, porém, a contradição é um tanto acidental. Eu não desconsideraria a legitimidade de nenhum dos casos de uso; se ambos fossem implementados, eu poderia ver uma palavra-chave extra evitando conflito semântico. Sinceramente, sou indiferente se as palavras-chave acabarão de uma forma ou de outra - eu só quero digitar merda.

*: Acabei de perceber que você provavelmente usaria funções de ordem superior referenciando funções em parâmetros por seus nomes de parâmetro, em vez de capturar seus tipos em genéricos.
Parece que as coisas podem de fato ser convertidas em ambas as direções: typeof ajuda a elevar do nível de valor para o nível de tipo, enquanto declare let y: x; ajuda a elevar as coisas do nível de valor para o nível de tipo. Deselegante como a solução alternativa do OP, mas sim.
Se os tipos de função fossem trazidos, por exemplo, por genéricos explícitos, acho que isso não ajudaria - eles estariam no nível de tipo sem uma maneira de movê-los.
Se isso soa como se eu estivesse esperando preventivamente cobrir as bases da funcionalidade, provavelmente é porque muito do meu progresso em desafios de digitação não resolvidos foi bloqueado neste complemento (com menção notável ao 5453). Mas se conseguirmos descobrir essas coisas, valerá a pena.

Edit: Eu pensei em alguns casos teóricos que podem ser excluídos em uma abordagem baseada em expressão agora, embora nenhuma ocorrência prática tenha vindo à mente ainda:

  • funções passadas por meio de um genérico fornecido explicitamente.
  • funções retornadas por funções de parâmetro, para serem capturadas de forma genérica, ou seja (usando a sintaxe da proposta baseada em expressão) declare function f<G extends (...args: any[]) => R, R extends <T>(foo: T) => Bar<T>>(g: G): typeof R(baz); // error following the expression-based proposal: R is not exposed at the expression level . É claro que isso poderia ser calculado se você soubesse e pudesse fornecer os tipos de parâmetro de G , mas afaik não há como obter essas informações até agora (a menos que talvez #14400?). Suponho que essa categoria inclua funções que operam nas funções de fábrica usadas no AngularJS mencionadas acima.

@Igorbek Não entendo o que você espera com este trecho:

function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }

Parece uma definição circular.

@masaeedu desculpe, eu quis dizer:

(mudar para f em vez de someFunctionWithComplexReturnTypeThatUsesGenerics )

function use<T>(item: typeof f<T>(undefined as any as T)) { ... }

Apenas para elaborar, é isso que atualmente não pode ser contornado introduzindo uma variável falsa:

const _typeofItem = f<T>(undefined as any as T); // no T here
function use<T>(item: typeof _typeofItem) { ... }

BTW, acabei de perceber que a proposta atual está em conflito com o operador de tipo de consulta do tipo de membro T[K] já que typeof tem prioridade mais alta:

  • typeof x[K] atualmente significa (typeof x)[K] onde K _é_ um tipo
  • se a variação baseada em expressão for tomada, uma ambiguidade seria introduzida:

    • typeof x[k] significa (typeof x)[k] onde k é um tipo; ou

    • typeof x[k] significa typeof (x[k]) onde k é uma expressão

Na verdade, eu preferiria typeof (x[k]) porque eles são semanticamente equivalentes, mas é uma mudança de ruptura com certeza.

Quando usei as ferramentas de desenvolvedor do Chrome, percebi que o operador membro tem maior precedência. Onde você encontrou aquilo?

image

@Igorbek : sim, parece ser por isso que type Thing2Type = typeof things['thing-2']; do post original já funciona.

@dehli : você está comparando o nível de expressão JS ao nível do tipo TS - eles não são os mesmos. Um é executado em navegadores/nós, o outro no compilador TS.

Pessoalmente, acho estranho que o TypeScript resolva typeof de nível de tipo com uma precedência mais alta do que o acesso de tipo indexado. Quase parece um bug para mim, honestamente. (Eu me pergunto se esse caso é realmente testado.)

@tycho01 Peguei . Presumi que o TS usaria a mesma ordem de precedência do JS.

@dehli os níveis de expressão são os mesmos, sim. No nível de tipo, é outra coisa com sintaxe semelhante, mas retornando um tipo em vez de uma string.

Eu estou supondo que a precedência pode ser o resultado das limitações que eles imaginaram que tivesse. Admito que essas considerações podem não parecer mais tão sensatas se sua função deve ser expandida.

Em outra nota, se ambas as versões da proposta fossem implementadas, os colchetes serviriam como uma maneira eficaz de manter explícito em que nível estaríamos para isso também. Estou tendo problemas para pensar em coisas que não soam como compromissos, mas sim.

@Igorbek Este é o principal caso de uso restante para typeof em expressões arbitrárias? Se obtivermos #14400, isso nos daria returnof usando tipos simples definidos pelo usuário, se não me engano.

14400 não nos dará returnof em sua forma genérica:

type Return<T extends () => R, R = any> = R;

// overloaded function
declare function f(item: number): number;
declare function f(item: string): boolean;

type fType1 = Return<typeof f>; // ??
type fType2 = typeof f(1); // number
type fType3 = typeof f('a'); // boolean

// generic function
declare function g<T>(item: T): T;

type gType1 = Return<typeof g>; // ??
type gType2 = Return<typeof g<number>>; // not supported syntax
type gType3 = typeof g(1); // number
type gType4 = typeof g<number>(1); // number
type gType5 = typeof g('a' as 'a'); // 'a'

Eu não estou ciente de todos os casos de uso restantes, eles parecem ainda não terem sido descobertos, mas os mais imbatíveis e atraentes para mim são:

  • _tipos mapeados condicionais_ que são muito mais poderosos do que poderíamos obter com #12424, porque typeof usaria a resolução de sobrecarga padrão; @tycho01 mostrou um exemplo elegante nos tipos promised PR #17077
  • obtendo tipos de retornos de função/método no contexto de tipos genéricos (como mostrei antes)

@masaeedu : Boa pergunta. A versão curta é basicamente o que @Igorbek disse; para um pouco mais de detalhes dos desafios concretos resolvidos, você encontrará uma pequena lista na minha lista de 'principais recursos necessários' no nº 16392, onde tentei vincular desafios não resolvidos às propostas que poderiam habilitá-los.

Isso inclui:

  • funções de fábrica, como por exemplo, AngularJS, veja os três tópicos vinculados no post original aqui. - realisticamente, isso deve ser bom, dado o ReturnType recém-integrado.
  • Dadas as entradas conhecidas, você pode calcular de repente os tipos de retorno exatos para coisas como reduce / map / filter / find -- qualquer coisa que envolva iteração e verificações. stdlib.d.ts se beneficiariam lá, assim como libs FP como Ramda/Lodash. Na prática, nem todas as entradas podem ser conhecidas (mais arrays do tipo lista do que tuplas), mas as operações map e filter em objetos também podem ser digitadas melhor do que apenas Partial . Isso é necessário para digitar melhor as bibliotecas de gerenciamento de estado, como redux (consulte https://github.com/piotrwitek/react-redux-typescript/issues/1) e ngrx do Angular, que até então são forçados a manter a API do usuário em baixo nível porque sem uma operação map bem tipada não há como eles calcularem o resultado desejado no nível de tipo dos dados de entrada DRY'er.
  • Atualmente, as tipagens de composição de funções também não são capazes de levar em consideração os tipos de retorno dependentes de entrada.
  • De repente, também tornaria viável começar a digitar coisas como lentes. - Suponho que tecnicamente isso ainda esteja bloqueado aqui em operações de edição com tipos de retorno dependentes de entrada. que é um luxo mesmo. alterar tuplas ainda precisa (parte de) #5453.
    Também permite:
  • operando em literais booleanos no nível de tipo, o que até agora era impossível
  • tipos de desempacotamento para operações semelhantes a flatMap , como essa proposta promised
  • adicionando restrições à entrada de tipo/função, o tópico de #15347 e uma ideia que obtive do livro 'Desenvolvimento orientado por tipo com Idris' sobre tipos dependentes. Isso pode ajudar a dizer não permitir divisores 0 -- essencialmente um truque para subtrair de tipos, o tópico de #4183. Tenho certeza de que ainda não imaginei a maioria dos casos de uso, mas isso poderia ser feito com isso, por exemplo, function div<B extends number, NotZero = { (v: '1') => 'whatever'; }({ (v: 0) => '0'; (v: number) => '1'; }(B))>(a: number, b: B) . Normalmente, a linguagem Idris anuncia com operações de vetor/matriz de comprimento seguro, mas isso só precisa disso para funções de ordem superior.
  • Uma maneira de expressar restrições Union<T> / NonUnion<T> em tipos de entrada usando as restrições acima + IsUnion . -- esse tipo meio que quebrou e eu não tenho mais certeza de como fazer isso.
  • dado #5453 também, isso também ajuda a digitar funções como curry , Function.prototype.bind , Promise.all (não uma lista exaustiva, apenas algumas funções que outras pessoas levantaram como desafiadoras).
  • Lógica switch proposta em #13500, aqui resolvida através da correspondência de padrões de sobrecarga de função
  • aqueles tipos condicionais mapeados que são o tópico de #12424 e #17077 ( promised ), também conhecidos como verificações de tipo em nível de tipo. No entanto, isso não resolveria - mesmo que isso fosse utilizável para produzir literais booleanos em qualquer lugar hoje, até esse #6606 chegar, ainda não tínhamos como operar / fazer condicionais com base em tais literais booleanos no nível de tipo ainda de qualquer forma.
  • verificando a presença de índice de string em tipos de objeto, como preservar isso nas operações (como Omit , Overwrite de #12215), veja meu comentário em #12424
  • Com base nas verificações de índice de string acima, uma maneira de corrigir o acesso à propriedade do tipo de objeto, de modo que verifica se há strings correspondentes aos nomes dos métodos de protótipo de objeto (por exemplo toString , toLocaleString ) resolverá para o índice de string em vez do que para os métodos de protótipo, potencialmente corrigindo falhas relacionadas ao acesso ao objeto toString em todas as outras operações de tipo que anteriormente dependiam do acesso à propriedade do objeto interno com falha.

edit: casos de uso riscados preenchidos por tipos condicionais (#21496) desde o momento da escrita.

A proposta de @Igorbek de uma abordagem de expressão em primeiro lugar (ao custo de talvez quebrar algumas aplicações desconhecidas de parte da lista acima - ainda não pensei em nada concreto, embora tenha medo de ser pintado em um canto ) também prometem fechar a lacuna entre os níveis de valor e tipo, que atualmente incluem coisas como este aplicativo de função (este #6606), spread/rest (#5453), operador de asserção ! (#17370), tipo de união subtração através de guardas de tipo (#4183, de outra forma alcançável através das restrições mencionadas acima), e possivelmente mais que eu não consigo pensar em cima da minha cabeça.

Eu acho que toda essa troca pode levantar algumas questões como por que tornar a mesma funcionalidade acessível de duas maneiras (nível de tipo real, nível de expressão acessado embutido no nível de tipo).

Editar: atualizei meu comentário de comparação acima com um desafio potencial extra.

Edição 2:

É lamentável aqui que o post original faria você acreditar que isso só permitiria escrever alguns casos extremos sem alguma solução alternativa null & , enquanto na realidade esse é o recurso crítico que impede o TS agora sem soluções alternativas conhecidas para contextos de ambiente .

Isso significa que afeta TS escritos em arquivos .d.ts separados, que é a norma não apenas para stdlib.d.ts , mas também para todos os tipos de DT escritos separadamente de seus projetos JS originais, o que raramente muda enquanto as bibliotecas JS querem permanecer abertas a alternativas como o Flow também.

(Isso não é muito melhor para tipos .ts , pois os tipos que usam a 'solução alternativa' do OP não podem ser parametrizados, como compor em tipos reutilizáveis ​​de nível superior sem influenciar o nível de expressão.)

@tycho01 Obrigado pela lista; há muitos links lá que eu preciso digerir. Eu realmente quero correspondência de padrões em tipos também, e #6606 é uma solução boa e pragmática para o problema. No entanto, parece-me que há uma confusão crescente entre as coisas nos níveis de valor e tipo, e o #6606 não melhorará as coisas nessa área.

A razão pela qual esse recurso é necessário é porque não temos como construir expressões de tipo que correspondam a:

  • o tipo de retorno de um tipo de função, quando aplicado com argumentos de certos tipos
  • (antes de typeof K[L] ) o tipo de uma propriedade em um tipo de objeto que corresponde a uma chave de um tipo literal de string
  • (possivelmente outros, preciso examinar sua lista mais de perto)

Eu sinto que deveria ser possível construir puramente expressões de nível de tipo para eles, sem recorrer a misturar construções de nível de valor e expressões de nível de tipo. Seria bom se tipos como (a: A) => B ou A | B fossem açúcar em tipos parametrizados simples como Func<A, B> , Union<A, B> , e tivéssemos ferramentas gerais para manipular tipos parametrizados (HKTs, fundeps ou famílias tipo, etc.).

Eu sei que um foco excessivo na solidez não é um dos objetivos do TypeScript, mas agora existem muitos conceitos de nível de tipo que interagem juntos de maneiras opacas. Algum tipo de formalização do que é um tipo e uma maneira de prescrever regras mecânicas de como um tipo interage com outros tipos (subtipagem, desestruturação etc.) ajudaria muito.

Eu sinto que deveria ser possível construir puramente expressões de nível de tipo para eles, sem recorrer a misturar construções de nível de valor e expressões de nível de tipo.

Ah, sim, eu pessoalmente não pretendia recorrer ao uso de construções de nível de valor - isso é tudo o que estou tentando. Se o nível de valor fosse indispensável, eu poderia estar no campo da expressão aqui. :P

Eu acho que a diferença entre os níveis de valor e tipo deve diminuir (o TC39 se move mais rápido que o TS?), e afaik as lacunas já têm uma excelente proposta de nível de tipo (veja a parte inferior do meu post anterior).

Caramba, eu percebo que construir tipagens para algumas das funções estará fora da experiência da maioria dos usuários.
A maneira como vejo isso é que quero que a biblioteca padrão e as bibliotecas FP sejam digitadas tão bem que os usuários do TS possam apenas escrevê-las e ter a inferência automaticamente cuidada por elas.
O TS tem apenas alguns operadores de nível de tipo, mas resolver problemas reais com eles (o principal exemplo é Overwrite / Omit do #12215) pode parecer pouco menos que ciência do foguete para desenvolvedores da web casuais. Caramba, nos levou até recentemente também, e eles ainda não são à prova de protótipo / índice / símbolo.

Seria bom se tipos como (a: A) => B ou A | B eram açúcar em tipos parametrizados simples como Func , Union

Podemos inverter e criar os tipos parametrizados como aliases/construtores de tipo. Para uma operação de tipo que recebe um Foo<Bar> , não importa se sua coisa simplifica para um - apenas verifica se ele se encaixa na descrição.
Isso é basicamente o que stdlib.d.ts faz -- você tem um foo[] , mas ele satisfaz a descrição Array<Foo> e, portanto, funciona com seus tipos Array.prototype .
Não é assim que realmente ajuda:

type Union2<A, B> = A | B;
type TuplizeA<Tpl extends Union2<any, any>, A, B> = [Tpl[0], Tpl[1]];
// ^ sorry, no way to access union members through e.g. [0]
// type a = TuplizeA<1 | 2>;
type TuplizeB<Tpl extends any | any> = [Tpl[0], Tpl[1]];
// ^ sorry, no way to access union members through e.g. [0]
// type b = TuplizeB<1 | 2>;
type TuplizeC<Tpl extends Union2<A, B>, A, B> = [A, B];
type c = TuplizeC<1 | 2>;
// ^ need 3 arguments, maybe fixable with #14400
type TuplizeD<Tpl extends A | B, A, B> = [A, B];
// ^ need 3 arguments, maybe fixable with #14400
type d = TuplizeD<1 | 2>;

Então, sim, não resolvi, mas acabei de perceber que o # 14400 do kube poderia realmente ajudar lá. E eu acabei de te ver lá mais cedo hoje também!
A mesma coisa para funções, na verdade - o que é um bom lembrete de que #14400 pode não apenas fazer tipos de retorno de função lá, mas também tipos de parâmetro.

No momento, essa abordagem precisaria usar sobrecargas, o que é lamentável, pois não é dimensionado, mas sim. Para torná-lo genérico novamente, usaríamos essencialmente essas sobrecargas de correspondência de padrões #6606 para acionar a opção certa para diferentes aridades.
Suponho que alguém poderia usar isso para convertê-los genericamente em tipos de tupla e, em seguida, iterar sobre eles usando minha abordagem de incremento para operar neles de alguma forma.
Para os sindicatos, eu esperava uma maneira mais bonita de converter para tipos de tupla. No entanto, adicionaria uma ordem arbitrária e também não conseguiria pensar em uma sintaxe/palavra-chave bonita.

Edit: para passar por essas lacunas de funcionalidade de nível de expressão/tipo novamente:

  • aplicação de função: este #6606
  • operador de asserção ! (#17370): depois deste #6606 isso é resolvido
  • subtração de tipo de união através de guardas de tipo (#4183): apenas um caso geral de ! acima, com este #6606 também alcançável por meio de restrições (por exemplo NotZero acima).
  • spread/rest (#5453) -- mesmo que as tuplas não possam ser manipuladas até que isso chegue, os ArrayLike s semelhantes podem, veja as operações List no meu gist . com este #6606, agora acho que poderíamos extrair parâmetros de funções não aplicadas, embora extraí-los no momento da aplicação (ou seja, para obter seus valores de entrada precisos), ainda precisaria de 5453.

Resumindo, se essa proposta chegar, eu diria que uma lacuna de funcionalidade entre os níveis de expressão e tipo não seria um argumento muito grande para a proposta com sabor de expressão aqui. Se 5453 estivessem também, eu não conseguia mais pensar em nenhum lá. E observe que, para raras exceções, a solução alternativa observada na postagem original aqui ainda seria válida.

Agora, um argumento que ainda pode ser feito facilmente para isso seria que com a variante com sabor de expressão, mesmo antes que o nível de tipo alcance os operadores de espelhamento, essa funcionalidade seria exposta sob a mesma sintaxe do JS (sem soluções alternativas), reduzindo a curva de aprendizado.

Edição 2:

Acabei de perceber que o truque da proposta de expressão de trazer tipos para o nível de expressão, 1 as any as MyType , deveria funcionar logicamente para a própria função também.

Isso provavelmente significa que a funcionalidade real habilitada por ambos os sabores parece um pouco semelhante, as diferenças externas consistem principalmente em typeof myVar (tipo de sabor) vs. myVar (expressão de sabor) para usar variáveis ​​no aplicativo de função; para usar tipos neles MyType (sabor de tipo) vs. 1 as any as MyType (sabor de expressão, alternativa declare let a: any; então <MyType>a ).

As mudanças de AST também parecem bastante gerenciáveis. O sabor da expressão precisa apenas do conjunto typeof para apontar para uma expressão de valor; type flavor copiaria a sintaxe do aplicativo de função existente ( fn<T>(arg) ) da expressão para o nível do tipo, conectando-se à implementação existente conforme proposto por Ryan acima.

Acho que então se resume ao seguinte:

Caso para sabor de expressão:

  • typeof expr com sintaxe JS sem solução alternativa antes do suporte ao nível do tipo TS

Caso para tipo de sabor:

  • sem alterações de quebra sobre a prioridade
  • expressões de valor ainda capturáveis ​​por meio de solução alternativa (ou por meio de TS se estiver sob sintaxe diferente até que os operadores alcancem)
  • MyType em vez de 1 as any as MyType : nenhum nível de tipo em seu nível de expressão em seu nível de tipo em seu nível de expressão.

Um tópico relacionado até agora intocado aqui foi como fornecer ligações this nesta 'aplicação' de função de nível de tipo. Agora, os delegados JS sobrescrevem isso nos métodos Function.prototype , mas, como está, eles não têm uma maneira de lidar com isso no nível do tipo.

Sintaxe de exemplo aleatório, dado um tipo de função F (this: Foo, a: number, b: string) => SomeReturnType que poderia ter sido invocado como F(MyA, MyB) : F(this: MyFoo, MyA, MyB) .

Calcular o tipo de retorno sem substituir a ligação this ainda seria como F(MyA, MyB) , espelhando como o argumento de nível de tipo this normalmente é ignorado se você tentar usar essa função em o nível de expressão.

Prós desta sintaxe de exemplo:

  • espelha a sintaxe da declaração

Contras desta sintaxe de exemplo:

  • espelha a sintaxe da declaração

Então, acontece que isso já está na linguagem!

Não fique muito animado.

@DanielRosenwasser acabou de me apontar para #12146, um bug que permite que chamadas de função em literais sejam usadas no lugar de tipos.

_5 minutos depois_

Surpresa! Uma coisa horrível e maligna que nunca devemos usar na produção. Mas é tentador...

interface String {
    passthrough<T>(v: T): T;
}

// All work
type ANumber = "".passthrough(10 * 10);
type AString = "".passthrough("hello" + "world");
type AHelloWorld = "".passthrough("hello world");
type AnArbitraryThing = "".passthrough(Object.assign({hello: "world"}, {foo: "bar"}));
type ThisCraziness = "".passthrough((() => "cows are big dogs"));

~Isso faz com que o Effort: Difficult nesta questão pareça um pouco duvidoso, parece que eles fizeram isso por acidente ali.~ Eu leio mais e me sinto bobo, isso _é_ difícil de fazer bem.

Divirta-se @tycho01.

@TheOtherSamP Eu tentei isso com o TypeScript 2.4.2 e todos esses tipos são inferidos como any .

@pelotom Huh, está funcionando aqui em 2.4.2 e 2.5.0-dev.20170803. Target es6 e modo estrito.

image

Parece que eles acabaram de consertar, mas temo que pode ser minha culpa. #17628

@TheOtherSamP Não, sem dados. Ah bem.

@pelotom Isso é estranho, está funcionando em um projeto completamente novo para mim, não sei o que seria diferente em nossas configurações.

@TheOtherSamP : haha, isso é muito engraçado.
Em termos de tempo, parece que eles começaram a correção um pouco antes do seu comentário. Ah bem.

@pelotom :

Eu tentei isso com o TypeScript 2.4.2 e todos esses tipos são inferidos como qualquer.

Seu trecho parece funcionar no Playground (2.3.2). Em uma versão recente fora dela ( ^2.5.0-dev.20170626 ) estou tendo problemas para reproduzir também.

Isso faz com que o Effort: Difficult nesta questão pareça um pouco duvidoso, parece que eles fizeram isso por acidente ali.

Eles estavam se referindo a uma implementação baseada em tipo, o que significaria algumas mudanças, enquanto isso parece usar linguagem de expressão (-> + , Object.assign , outras chamadas de função).

@tycho01 Eu _acho_ que tudo começou por chamar a atenção para isso em #17618. Ah, bem, isso vai me ensinar a pensar seriamente em usá-lo na produção.

Eles estavam se referindo a uma implementação baseada em tipo, o que significaria algumas mudanças, enquanto isso parece usar linguagem de expressão (-> +, Object.assign, outras chamadas de função).

Sim, eu fui um idiota e não li toda essa questão até depois de dizer isso. É uma pena, provavelmente é uma versão melhor do recurso, mas gostaria que pudéssemos tê-lo agora. Eu empurro muito os limites do sistema de tipos, e isso ou #12424 abriria tantas opções.

Abriu um PR em #17961.

@yortus Isso cobre o caso de 'typeof literal'?
O TypeScript hoje não permite escrever "const x: typeof 1 = 1;"

@NN--- a proposta original abrange todas as expressões, mas pelo que entendi, a parte 'aprovada' abrange apenas o acesso à propriedade e os tipos de retorno de função.

Mesmo que typeof 1 fosse permitido, não tenho certeza se daria o tipo literal ( 1 ) ou o tipo mais amplo ( number ).

O TypeScript hoje não permite escrever "const x: typeof 1 = 1;"

Por que não const x: 1 = 1; ?

@SaschaNaz eu queria escrever algo como

const a = {q:1};
const b = {q:1};
const x: ReadonlyArray<typeof a> = [a,b];

Mas semelhante não funciona com literais:

const x: ReadonlyArray<typeof 1> = [1,2,3];

@yortus Bom ponto sobre o tipo exato. Não pensei em tipos literais ..

@NN---: Acredito que seu exemplo já funcione.

@tycho01 Flow agora tem o tipo $Call para obter o tipo de retorno da função https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4

Permitir apenas typeof fn(...) não seria o mesmo que permitir typeof de uma expressão arbitrária?

function fn() {
  return /** whatever expression you like */;
}
type a = typeof fn();

exceto que agora você está criando uma função de tempo de execução para nenhum outro propósito além de descobrir um tipo?

Na verdade. Você está fazendo uma avaliação de tipo de uma expressão, não uma execução da expressão.

@dyst5422 typeof fn() não avaliaria a expressão, apenas forneceria o tipo de retorno.

EDIT: talvez seja isso que você estava tentando dizer, não tenho certeza. Mas acho que @sky87 estava falando sobre _definir_ uma função sem propósito, exceto usá-la em uma expressão de tipo, não _avaliando_ ela.

@dyst5422 , como @pelotom disse, não quis dizer que você executaria a função. Para elaborar mais: se você não permite o typeof de expressões arbitrárias, mas permite o typeof do tipo de retorno de uma função, o que farei para descobrir o tipo de expressões é envolvê-los em uma função para que eu possa pedir seu tipo de retorno. Isso cria uma função em tempo de execução apenas para obter seu tipo de retorno, e é mais clichê para escrever.

EDIT: você pode realmente descobrir o tipo de expressões arbitrárias já, isso é feio, mas funciona

const dummy = (false as true) && /* arbitrary exp */;
type TypeOfExp = typeof dummy;

Sinceramente não sei qual hack eu prefiro. Eu acho que a melhor coisa seria poder pedir diretamente o tipo usando typeof .

Ah, eu sigo agora. Sim, acho que o caminho preferível seria poder usá-lo como

type TypeOfExp = typeof (
  false &
  "false" &
  0
)

ser capaz de arbitrariamente fazer avaliação de tipo de expressão

Será possível consultar o tipo de retorno de uma invocação new ? Meu caso de uso: Quero escrever anotações de tipo para uma função que aceite uma referência a qualquer implementação PromiseConstructorLike (por exemplo, $q ou Bluebird) e retorne uma Promise construída por essa implementação.

declare function wait<P extends PromiseConstructorLike>(time: number, implementation: P): typeof new implementation<void>((res: any, rej: any) => void);

const bluebirdPromise = wait(1e3, Bluebird);
// typeof bluebirdPromise should be instance of Bluebird

Será possível consultar os tipos de retorno sem typeof , ou teremos que null as FnType ?

interface Fn {
    (a: string): string;
    (a: number): boolean;
}
type Ret = Fn(number); // Ret = boolean
type Ret = typeof (null as Fn)(number);

Desculpe se essas perguntas já foram respondidas; Eu não conseguia encontrá-los.

Qual é o ponto de new aqui, você não quer apenas typeof implementation() ?

Não, porque implementation() não é uma chamada válida. PromiseConstructorLike só pode ser invocado via new , por suas declarações de tipo. typeof implementation() é um erro de tipo, assim como (typeof implementation)['foobar'] seria um erro de tipo.

Link do playground

É possível introduzir tipos genéricos inferíveis como o que o FlowType fez? Pelo menos, pode resolver o problema de obter o tipo de valor de retorno das funções.

type _ExtractReturn<B, F: (...args: any[]) => B> = B;
type ExtractReturn<F> = _ExtractReturn<*, F>;

@Cryrivers : veja #14400 para essa abordagem. Na verdade, ele não resolve o problema em que o tipo de saída depende da entrada.

Acabei precisando disso novamente hoje para sugerir o que uma chamada para uma função retornaria dinamicamente, com certeza gostaria que tivesse prioridade.

Um operador ReturnType<T> está sendo adicionado a lib.d.ts no TS 2.8, alimentado por tipos condicionais.

Como ReturnType<T> ainda precisa levar em conta os tipos de retorno dependentes dos tipos de argumento, para referência, aqui está a implementação do tipo $Call do Flow.

edit: desculpe @goodmind , eu não tinha percebido que você já havia vinculado exatamente isso.

Atualizei minha postagem anterior de casos de uso desta proposta (ou sua interpretação de chamada de tipo) com base nas recentes adições de TS.
Os casos de uso de correspondência de padrões agora são cobertos por #21496, deixando... os casos em que queremos calcular tipos com base em lambdas fornecidos pelo usuário, por exemplo, curry , composição de funções, map , reduce , edição de lentes baseada em um lambda... as coisas divertidas. :)

PS @thorn0 : Acho que seu caso de uso Angular pode ser preenchido com ReturnType (#21496) agora!

Eu acho que isso deve ser coberto permitindo essas expressões.
prop2: typeof this.prop1.big.complex;

@mhegazy Existe um problema separado para rastrear isso?
É irritante que typeof funcione para locais, mas não para propriedades, enquanto trabalha para propriedades estáticas.

class A {
    x: number;
    static y: number;
    f() {
        const a: number = 1;
        const b: typeof a = 2; // OK
        const c: this.x = 3; // No :(
        const d: this['x'] = 3; // OK
        const e: typeof A.y = 4 // OK
    }
}

@NN--- você sempre pode usar tipos indexados para isso:

this['x']

@cameron-martin Não funciona. Parque infantil

@tycho01 a nova inferência e condicionais de tipo são incríveis, mas também super irritantes por não funcionarem para sobrecarga de funções. A razão dada foi que precisa de algo assim typeof para resolver o tipo de função entre os possíveis.

@NN apenas use const d: this['x'] = 3;

Oh legal :)

@NN--- ou use

class A {
    x: number;
    static y: number;
    f() {
        const self = this;
        const a: number = 1;
        const b: typeof a = 2; // OK
        const c: typeof self.x = 3; // OK
        const d: typeof self['x'] = 3; // OK
        const e: typeof A.y = 4 // OK
    }
}

@tsofist Estou ciente de que o local funciona, mas acho isso feio.
É o mesmo que salvar 'this' manualmente para o retorno de chamada 'function(){}' em vez de usar lambda com captura implícita.

@NN

isso feio.

sim, é apenas uma opção :)

Os tipos condicionais agora tornam isso amplamente irrelevante, pois você pode escrever ReturnTypeOf<T> junto com outros aliases específicos se quiser validar um conjunto específico de argumentos. Eles não podem fazer a resolução de sobrecarga, mas não achamos que esse recurso valha a complexidade apenas para esse caso de uso.

@RyanCavanaugh Eu acredito que você quer dizer ReturnType<T> ?

@RyanCavanaugh isso é lamentável - a resolução de sobrecarga é o que eu realmente precisava. Existe outro problema para rastrear a adição de resolução de sobrecarga a tipos condicionais/inferiores?

Você deve ser capaz de escrevê-lo:

type Return1<A1, T extends (a: A1) => any> = T extends (a: A1) => infer R ? R : any;
type Return2<A1, A2, T extends (a: A1, a: A2) => any> = T extends (a: A1, a: A2) => infer R ? R : any;

declare function something(a: number): number;
declare function something(a: string): string;
declare function something(a: number, b: string): boolean;

type A = Return1<number, something>; // number
type B = Return1<string, something>; // string
type C = Return2<number, string, something>; // boolean

Eu não testei, porém, e você precisaria de um auxiliar separado para cada número de argumentos.

@ForbesLindesay : o something é atualmente uma variável de nível de expressão - referenciando-o com, por exemplo typeof aqui (ou declarando-o como uma interface) corrige isso. Na verdade, não estou conseguindo fazer com que ele produza tipos de retorno apropriados (em 2.8.0-dev.20180318 ).

@ForbesLindesay infelizmente não acredito que funcione; o mecanismo de inferência escolherá a sobrecarga do método _last_:

type Funcs = ((p1: string, p2: string) => void) & ((p1: number) => void);

type FuncPromise1<T> = T extends (p1: infer P1) => void ? (p1: P1) => Promise<[P1]> : never;
type FuncPromise2<T> = T extends (p1: infer P1, p2: infer P2) => void ? (p1: P1, p2: P2) => Promise<[P1, P2]> : never;

let foo: FuncPromise1<Funcs> & FuncPromise2<Funcs>;

image

No entanto , o mecanismo de inferência _é_ capaz de lidar com uniões de tuplas:

type Tuples = [string, string] | [number];

type TuplePromise1<T> = T extends [infer P1] ?  (p1: P1) => Promise<[P1]> : never;
type TuplePromise2<T> = T extends [infer P1, infer P2] ? (p1: P1, p2: P2) => Promise<[P1, P2]> : never;

let foo: TuplePromise1<Tuples> & TuplePromise2<Tuples>;

image

talvez precisemos de algo para permitir sobrecarga -> objeto e função -> objeto, desempacotando. Faça o mapeamento e a inferência de tipos lá, depois volte para uma função e sobrecarregue.

@MeirionHughes :

talvez precisemos de algo para permitir sobrecarga -> objeto e função -> objeto, desempacotando. Faça o mapeamento e a inferência de tipos lá, depois volte para uma função e sobrecarregue.

Gostou (a: number, b?: string) => boolean -> { a: number, b?: string } ? Não podemos obter nomes de parâmetros ainda assim, mas conceitualmente isso fica mais difícil para parâmetros de descanso ( (a: number, ...b: string[]) => boolean ), também porque não podemos usar tipos de objeto para fazer a ordem.

A ordem provavelmente importa mais do que os nomes, e podemos converter entre parâmetros e tuplas. Spreads/opcionais ainda podem complicar um pouco também.

Isso reduz o problema de extrair as sobrecargas. As sobrecargas devem ser uma interseção de tipos de função como ((a: number) => 123) & ((s: string) => 'hi') , então o problema é como 'desempacotar' um tipo de interseção (por exemplo, um tipo de tupla) -- a partir de agora, não temos isso.

Acho esse caminho insatisfatório, pois abordaria o caso de uso de sobrecarga, mas não diria genéricos, mas sim. O desdobramento da interseção ainda era uma lacuna de qualquer maneira.

Uma vez que esta questão está encerrada, já existe uma nova proposta para as peças que ainda faltam? Como uma maneira de lidar com o tipo de retorno dependendo dos argumentos?

Uma vez que esta questão está encerrada, já existe uma nova proposta para as peças que ainda faltam?

nenhum que eu conheça.

Como uma maneira de lidar com o tipo de retorno dependendo dos argumentos?

não acho que temos um problema de rastreamento de tipos de chamadas.

Existe suporte preliminar para a ideia de apenas adicionar o aplicativo de função de nível de tipo? Eu poderia escrever uma proposta para isso. Sintaticamente, acho que esse é o caminho mais fácil.

type MyType<A> = {
    foo: A
}

type Wrap = {
    <T>(maybe: MyType<T>): MyType<T>;
    (maybe: any): MyType<any>;
}

type Naive = ReturnType<Wrap>; // Naive = { foo: any }
type Proposed1 = Wrap(maybe: number); // Proposed1 = { foo: number }
type Proposed2 = Wrap(maybe: MyType<number>); // Proposed2 = { foo: number }
type Proposed3 = (<T>(maybe: T) => MyType<T>)(maybe: number) // Proposed3 = { foo: number }

Casos de borda:

const foo = <T>(a: T) => T:

type Edge1 = (typeof foo)(a: number) // Probably trivial?

type Foo = {
    <T>(a: string): T
}

type Edge2 = Foo<number>(a: string) // Should this be allowed? Probably not, because:

type Bar<A> = {
    (a: string): A
}

type Edge3 = Bar<number>(a: string) // Things are getting strange

interface Baz<A> {
    <T>(a: T): T | A
}

type Edge4 = Baz<number>(a: string) // What is this?

Existe suporte preliminar para a ideia de apenas adicionar o aplicativo de função de nível de tipo? Eu poderia escrever uma proposta para isso. Sintaticamente, acho que esse é o caminho mais fácil.

Não no momento. Nós realmente não queremos colocar a resolução de sobrecarga no espaço do tipo de ordem superior; o processo é bastante complexo e inclui várias passagens para inferir parâmetros de tipo e argumentos de tipo contextual, etc. tempo sendo.

@mhegazy mudou a postura da equipe sobre isso, dado o trabalho recente em #24897 ??

Parece haver alguns problemas em torno de cujas soluções podem ser reduzidas a um tipo $Call , e um tipo $Call abriria a porta para uma maneira relativamente direta de emular tipos de tipo superior; veja https://gist.github.com/hallettj/0fde5bd4c3ce5a5f6d50db6236aaa39e por exemplo (substitua o uso de $PropertyType e $ObjMap por $Call ). EDIT: Exemplo adicional: https://github.com/facebook/flow/issues/30#issuecomment -346674472

Esse recurso provavelmente estaria alinhado com o histórico do TypeScript de encontrar uma solução comum razoável para muitos problemas, não?

Os tipos condicionais agora tornam isso amplamente irrelevante, pois você pode escrever ReturnTypeOf<T> junto com outros aliases específicos se quiser validar um conjunto específico de argumentos. Eles não podem fazer a resolução de sobrecarga, mas não achamos que esse recurso valha a complexidade apenas para esse caso de uso.

@RyanCavanaugh @mhegazy

Eu concordo que é possível fazer coisas usando tipos condicionais. Acho que não traria muita complexidade adicional no compilador se reescrevermos User.avatar para User extends { avatar: infer T } ? T : never ? Então, por exemplo, poderíamos escrever

export type Avatar = User extends { avatar: infer T } ? T : never;

Como

export type Avatar = User.avatar;

para melhorar a legibilidade.

Exemplo completo

Suponha que carreguemos e transformemos alguns dados e acabemos com uma função findUser como esta

export function findUser() {
  return {
    username: 'johndoe',
    avatar: {
      lg: '1.jpg',
      s: '2.jpg'
    },
    repos: [
      {
        name: 'ts-demo',
        stats: {
          stars: 42,
          forks: 4
        },
        pull_requests: [
          { date: '2019-08-19', tags: ['bug', 'agreed-to-cla'] },
          { date: '2019-08-10', tags: ['bug', 'includes-tests'] },
          { date: '2019-08-07', tags: ['feature'] }
        ]
      }
    ]
  };
}

Graças à inferência de tipos mapeados, podemos extrair o tipo da função assim:

export type User = ReturnType<typeof findUser>;
export type Avatar = User extends { avatar: infer T } ? T : never;

Sugestão: isso deve avaliar a mesma coisa

export type Avatar = User.avatar;

Além disso, podemos até afirmar que User.avatar não deve ser do tipo never .

Mais exemplos

export type Repositories = User extends { repos: infer T } ? T : never;
export type Repository = User extends { repos: (infer T)[] } ? T : never;
export type RepositoryStats = Repository extends { stats: infer T } ? T : never;
export type PullRequests = Repository extends { pull_requests: (infer T)[] } ? T : never;
export type PullRequest = Repository extends { pull_requests: (infer T)[] } ? T : never;
export type Tags = PullRequest extends { tags: infer T } ? T : never;
export type Tag = PullRequest extends { tags: (infer T)[] } ? T : never;
export type Repositories = User.repos;
export type Repository = User.repos[];
export type RepositoryStats = User.repos[].stats;
export type PullRequests = User.repos[].pull_requests;
export type PullRequest = User.repos[].pull_requests[];
export type Tags = User.repos[].pull_requests[].tags;
export type Tag = User.repos[].pull_requests[].tags[];

Ao mapear uma propriedade aninhada de uma só vez, não fica muito claro o que está acontecendo

export type Tag2 = User extends { repos: { pull_requests: { tags: (infer T)[] }[] }[] } ? T : never;

Isso esclareceria muito

export type Tag = User.repos[].pull_requests[].tags[];

Estojo de canto

export class Hello {
  static world = 'world';
  world = 42;
}
export type ThisWillBeANumber = Hello extends { world: infer T } ? T : never;
export type ThisWillBeANumber = Hello.world;
export type ThisWillBeAString = (typeof Hello) extends { world: infer T } ? T : never;
export type ThisWillBeAString = (typeof Hello).world;

@lukaselmer Parece que você só quer

export type Avatar = User["avatar"];

que funciona hoje

@lukaselmer Parece que você só quer

export type Avatar = User["avatar"];

que funciona hoje

Isso é exatamente o que eu estava procurando. Procurei na documentação , mas não encontrei. Obrigado!

Isso faz parte do manual ou existe alguma documentação oficial sobre como isso funciona? Eu mesmo estou bastante familiarizado sobre como usá-lo, mas quando tento direcionar as pessoas para a documentação, tudo o que consigo encontrar é o tipo de guardas, o que é realmente completamente diferente

Então, notei que essa proposta saltou de 2015 e um dos objetivos originais era de alguma forma obter o tipo de uma única propriedade de uma interface.

interface a {
 foo: bar;
 /* more types */
}

const example = (fooNeeded: [magic] a.foo ) => {};

estou correto em supor que isso ainda não é possível 5 anos depois?

@MFry Acho que você está procurando por esta sintaxe: a['foo']

Já sabemos se existe uma solução para isso?

Estou tentando obter algo assim:

declare function something<A, B>(): void;

type Payload = string;

const hello = something<{}, Payload>();

declare function doThing<T extends ReturnType<typeof something>>(arg: T): { payload: unknown };

doThing(hello).payload === 123; // this should validate to a string aka type Payload

https://www.typescriptlang.org/play/index.html?ts=4.0.0-dev.20200512#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwGccBbEDACy1QHMAeAQQBp4AhAPgAoBKALngDccWYAG4AUGIwBPAA4IAClCkQcUYPAC8hDDCrVxYsHgIZ45EBBWbCJMpRq0A3gF9mi5auCcuB0JFgIKOjYePDAOAAq9nQR8CAAHhggqMAE8ABKZMgwqBGyILTScjiINqQUemycsNR8EbzwjvAySipqfGgA1qg4AO74zr6R0RzmljhcAHQtHmqaGloAjABMAMwi8AD0m -AVaQTkOMgQ6vxQEMJQSbs48FDaujR3nfdFCq2eYkA

Oi @maraisr , não tenho 100% de certeza do que você está tentando alcançar. No seu exemplo something aceita dois tipos mas não os usa, e hello é o valor de retorno de algo que sempre será void ; Então doThing nunca vê o tipo string em nenhum momento.

Talvez algo como abaixo é o que você quer?

declare function something<ReturnType>(): ReturnType;

type Payload = string;

const hello = () => something<Payload>();

declare function doThing<F extends () => any>(f: F): { payload: ReturnType<F> };

doThing(hello).payload === 'a string';

Ah sim - sinto muito por isso. Obrigado pela resposta rápida!! :100: @acutmore

O void era apenas para indicar que o tipo de retorno dessa função é irrelevante. Esses 2 tipos são encaminhados para outros tipos genéricos, que acabam sendo usados ​​em argumentos.

algo como:

declare function something<A, B>(a: MyComplexGeneric<A>, b: B[]): { somethingA: number, somethingB: number };

// Those 2 generics influence the return object so they do get used as such. And the 2 arguments are roughly that. Its an object and an array, more-or-less. 

Minha função doThing realmente não se importa com o que é o primeiro ( A ) genérico, mas se importa com o que é o segundo ( B ).

Veja que something no meu próprio caso de uso faz um efeito colateral que é lido pelo doThing .

Portanto, não posso simplesmente obter o ReturnType de uma função - preciso de alguma forma sugar o genérico de uma função criada.


Se você acha que essa consulta está além do escopo deste problema, continuarei minha jornada no StackOverflow!

@maraisr obrigado pelas informações extras.

Se você quiser doThing para poder obter o tipo B original de something , então ele precisa ser passado para hello de alguma forma. O TypeScript está apenas olhando para hello e sem alguma ajuda ele não saberá que é o tipo de retorno de something .

Esta é uma maneira que isso pode ser feito:

/** Create a type that can store some extra type information **/
interface SomethingResult<T> {
    __$$__: T;
    somethingA: number;
    somethingB: number;
}

declare function something<A, B>(): SomethingResult<B>;

type Payload = string;

const hello = something<{}, Payload>();

declare function doThing<Result extends SomethingResult<any>>(arg: Result): { payload: Result['__$$__'] };

doThing(hello).payload === 1123; // error because `payload` is of type string
interface User {
  avatar: string;
}

interface UserData {
  someAvatar: User['avatar'];
}

@RyanCavanaugh Por que isso está fechado? Tipos condicionais não resolvem este e muitos outros casos de uso, e se isso for mesclado, isso tornaria muitas coisas possíveis.

Estou trabalhando em uma função que pode transformar qualquer chamada de método em uma versão "sem pontos" (exemplo: [].map(() => n > 5) se transforma em map(() => n > 5)([]) e a única coisa que falta são os Tipos Condicionais e infer não pode detectar genéricos, então em funções genéricas alguns tipos sairão como unknown .

Se eu pudesse "chamar" as funções para obter o tipo ( typeof myFunc(() => Either<string,number>) ) seria possível ter essa funcionalidade (que no momento é impossível), e facilitar muitas outras coisas (HKTs, etc.. .)

A complexidade é muito alta para poder $Call uma função (como no fluxo)? Eu sinto que a datilografia já faz isso automaticamente.

@nythrox , não sentimos que a confusão sintática que isso pode levar seja superada pelos casos em que você precisa chegar a algum tipo. O caso específico de resolver uma expressão de chamada é rastreado em outro lugar; a proposta no OP de "permitir qualquer expressão" não é algo que achamos que seria uma boa opção para o idioma.

@RyanCavanaugh oh tudo bem, eu entendo. Obrigado pela resposta, você sabe quais problemas estão rastreando ao resolver uma chamada de função?

Pesquisei um pouco e não encontrei um problema para um tipo de utilitário de chamada de função; a única referência a um que encontrei foi em # 20352, que apenas vinculou a esse problema.

O caso específico de resolver uma expressão de chamada é rastreado em outro lugar

@RyanCavanaugh Você se importa de se conectar a outro lugar? 🙂

@tjjfvi #37181 é mais especificamente sobre a resolução de uma função com base em suas entradas. Pode ser o que você procura.

@acutmore Isso está um pouco na linha do que eu estava procurando, embora eu estivesse falando especificamente sobre um utilitário $Call de fluxo ou outra sintaxe para poder implementá-lo. A abordagem sugerida lá é estranha, mas obrigado pelo link.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

Antony-Jones picture Antony-Jones  ·  3Comentários

zhuravlikjb picture zhuravlikjb  ·  3Comentários

manekinekko picture manekinekko  ·  3Comentários

MartynasZilinskas picture MartynasZilinskas  ·  3Comentários

weswigham picture weswigham  ·  3Comentários