Typescript: Предложение: получить тип любого выражения с помощью typeof

Созданный на 25 янв. 2016  ·  157Комментарии  ·  Источник: microsoft/TypeScript

Рабочая реализация этого предложения

Попробуйте: npm install yortus-typescript-typeof

Посмотреть разницу: здесь .

Сценарий проблемы

Вывод типов в TypeScript очень хорошо подходит для большинства случаев. Однако остаются некоторые ситуации, когда нет очевидного способа сослаться на анонимный тип, даже если компилятор может его вывести. Некоторые примеры:

У меня есть строго типизированная коллекция, но тип элемента анонимный/неизвестный, как я могу сослаться на тип элемента? (# 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
Функция возвращает локальный/анонимный/недоступный тип, как я могу сослаться на этот возвращаемый тип? (#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 */) {...}
У меня есть интерфейс со сложной анонимной формой, как я могу ссылаться на типы его свойств и подсвойств? (#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
}

Зачем нам нужно ссылаться на анонимные/выведенные типы?

Одним из примеров является объявление функции, которая принимает анонимный тип в качестве параметра. Нам нужно каким-то образом сослаться на тип в аннотации типа параметра, иначе параметр должен быть набран как any .

Текущие обходные пути

Объявите фиктивную переменную с инициализатором, который выводит желаемый тип без вычисления выражения (это важно, потому что мы не хотим побочных эффектов во время выполнения, просто вывод типа). Например:

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

Этот обходной путь имеет несколько недостатков:

  • очень явно кладж, неясный для читателей
  • загрязнение идентификатора (необходимо ввести переменную типа dummyReturnValue )
  • не работает во внешних контекстах, потому что требует императивного оператора

Предложенное решение

_(Примечание: это решение уже было предложено в № 4233, но эта проблема помечена как «Требуется предложение», и есть несколько других тесно связанных проблем, поэтому эта отдельная проблема.)_

Разрешить операнду typeof быть произвольным выражением. Это уже разрешено для typeof expr в позиции значения, такой как if (typeof foo() === 'string') . Но это предложение также допускает произвольное выражение, когда typeof используется в позиции типа в качестве запроса типа, например, type ElemType = typeof list[0] .

Это предложение уже близко соответствует текущей формулировке спецификации:

Запросы типов полезны для захвата анонимных типов, генерируемых различными конструкциями, такими как литералы объектов, объявления функций и объявления пространств имен.

Таким образом, это предложение просто расширяет эту полезность на ситуации, которые в настоящее время не обслуживаются, как в приведенных выше примерах.

Синтаксис и семантика

Семантика точно такая же, как уже указано в спецификации 4.18.6 :

Оператор typeof принимает операнд любого типа и создает значение примитивного типа String. В позициях, где ожидается тип, 'typeof' также может использоваться в запросе типа (раздел 3.8.10) для получения типа выражения.

Предлагаемое отличие относится к цитируемому ниже разделу 3.8.10 , где будет удален зачеркнутый текст и добавлен жирный текст:

Запрос типа состоит из ключевого слова typeof, за которым следует выражение. Выражение обрабатывается как выражение идентификатора (раздел 4.3) или выражение доступа к свойству (раздел 4.13) унарное выражение , результатом которого становится расширенный тип (раздел 3.12). Подобно другим конструкциям статической типизации, запросы типов удаляются из сгенерированного кода JavaScript и не добавляют накладных расходов во время выполнения.

Следует подчеркнуть момент (который, как я думал, также был в спецификации, но не могу его найти), заключается в том, что запросы типа не оценивают свой операнд. Это верно в настоящее время и останется верным для более сложных выражений.

Это предложение не вводит никакого нового синтаксиса, оно просто делает typeof менее ограничивающим в отношении типов выражений, которые он может запрашивать.

Примеры

// 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: {}}}
}

Обсуждение плюсов и минусов

Против : Плохая эстетика синтаксиса. Альтернативные синтаксисы для отдельных случаев были предложены в № 6179, № 6239, № 4555 и № 4640.

For : другие синтаксисы могут выглядеть лучше для своих конкретных случаев, но все они отличаются друг от друга, и каждый решает только одну конкретную проблему. Это предложение решает проблемы, возникающие во всех этих вопросах, и разработчику не нужно изучать какой-либо новый синтаксис.

Против : выражение в позиции типа сбивает с толку.

Для : TypeScript уже перегружает typeof двумя значениями, поскольку запрос типа уже принимает выражение в позиции типа и получает его тип без его оценки. Это просто ослабляет ограничения на то, каким может быть это выражение, чтобы оно могло решить проблемы, поднятые в этой проблеме.

Против : этим можно злоупотреблять для написания огромных длинных многострочных запросов.

For : нет веских причин делать это в запросе типа, но есть веские причины разрешать более сложные выражения. По сути, это Мартин Фаулер, предлагающий и направляющий .

Влияние дизайна, вопросы и дальнейшая работа

Совместимость

Это чисто обратно совместимое изменение. Весь существующий код не затрагивается. Использование дополнительных возможностей typeof осуществляется по подписке.

Представление

Глядя на дифференциал, вы можете видеть, что изменения очень незначительны. Компилятор уже знает запрашиваемые типы, это просто показывает их разработчику. Я бы ожидал незначительного влияния на производительность, но я не знаю, как это проверить.

Инструменты

Я настроил VS Code для использования версии TypeScript с этим предложением, реализованным в качестве его языковой службы, и вся подсветка синтаксиса и intellisense безупречны, насколько я это тестировал.

Сложные выражения могут встречаться в файлах .d.ts

Операндом typeof может быть любое выражение, включая IIFE, или выражение класса с телами методов и т. д. Я не могу придумать ни одной причины для этого, просто это больше не ошибка, даже внутри файл .d.ts ( typeof можно использовать — и это полезно — в окружении). Таким образом, следствием этого предложения является то, что «утверждения не могут появляться в окружающем контексте» больше не является строго верным.

Рекурсивные типы обрабатываются надежно

Компилятор, кажется, уже имеет всю логику, необходимую для работы с такими вещами:

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;
}
Может запросить возвращаемый тип перегруженной функции

Это не двусмысленно; он выбирает перегрузку, соответствующую выражению запроса:

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

Самый полезный комментарий

Открыл PR под номером 17961.

Все 157 Комментарий

Для тех, кто хочет быстро поиграть с этим в VS Code с помощью intellisense и т. д., вот репозиторий игровой площадки .

тип P = тип foo (0); // P любое[]
тип Q = typeof foo(true); // Q является строкой

Я думаю, что использование типов в качестве аргумента вместо значений является более правильным синтаксисом.

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

Более ясно, что функция не вызывается, потому что вы предоставляете типы, а не значения в качестве аргументов. Другое дело, что это менее двусмысленно. Некоторые люди будут использовать typeof foo(false) , тогда как некоторые люди будут использовать typeof foo(true) . Если у вас есть типы в качестве аргументов, люди могут писать только typeof foo(boolean) .

@tinganho точно!
Хотя мы все еще можем написать typeof foo("abc") с #5185
здесь "abc" — тип одноэлементной строки

@tinganho Я обдумывал вашу идею и вижу некоторые вещи, которые мне нравятся в этом предложении, и другие вещи, которые мне нравятся в вашем предложении. Ваше предложение хорошо по причинам, которые вы указали (более простой и понятный синтаксис, менее двусмысленный, меньше похоже на вызов функции). Что я предпочитаю в своем предложении, так это то, что оно не вводит никакого нового синтаксиса, поэтому не добавляет никаких сложностей парсеру/проверщику, а также поддерживает более сложные сценарии, в которых у вас нет простых имен типов для аргументов.

Я подумал, что, если бы существовал очень сокращенный способ написать что-то вроде вашего синтаксиса foo(number) , но с использованием существующей механики разбора выражений? Поэтому в качестве эксперимента я ввел новое выражение: _unary as_. Вы можете просто написать as T и это сокращение от (null as T) . По сути, вы говорите: «Меня не волнует значение, но я хочу, чтобы выражение имело тип X».

С этим изменением (которое я реализовал в репозитории на игровой площадке ) вы можете написать что-то гораздо более похожее на предложенный вами синтаксис, но оно по-прежнему анализируется как обычное выражение:

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: {}}}

Это был просто быстрый эксперимент. Эквивалентный синтаксис может быть (но я этого не реализовал):

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: {}}}

Второй синтаксис можно назвать _нульярным утверждением_ типа, где выражение <T> является сокращением для (<T> null) .

@yortus , на прошлой неделе мы обсуждали это предложение. извините, что не отписался раньше. консенсус был 1. у нас есть проблема с невозможностью ссылаться на некоторые типы, например, возврат функции или тип экземпляра выражения класса. и 2. добавление выражений в позицию типа — это не то, что нам удобно.

Мы также обсуждали предложение @tinganho . Я думаю, что это более приемлемо, хотя, вероятно, будет сложнее реализовать. Добавление нового унарного оператора или использование синтаксиса приведения не так элегантно, как просто использование имен типов.

Сегодня долго обсуждали на слоге. «Принятие PR» здесь означает «Принятие PR при условии, что реализация не окажется слишком сумасшедшей».

Предложение @tinganho выглядит довольно хорошо (по крайней мере, по сравнению с другими вариантами), и мы хотели бы увидеть предварительный PR, который это реализует.

Хитрость заключается в том, что мы не хотим иметь полностью отдельный путь кода для разрешения возвращаемого типа f(number) против f(0) , но алгоритм разрешения перегрузки полностью встроен в предположение, что он работает с набором _выражений_, а не с набором _типов_. Но мы думаем, что с небольшой хитростью это должно быть просто.

Базовый план атаки будет таким:

  • Расширьте грамматику при разборе typeof , чтобы разрешить такие вещи, как вызовы функций, доступ к свойствам и доступ к индексированным свойствам.
  • При анализе аргументов вызова функции в запросе типа (назовем их _psuedocalls_ / _psuedoarguments_) используйте функцию parseType . Это создаст TypeNode , но установит флаг на узле, который указывает, что это был тип, проанализированный в контексте запроса типа.
  • В средстве проверки checkExpression проверяет наличие этого флага и вызывает getTypeFromTypeNode вместо обычной обработки выражений.

@mhegazy , @RyanCavanaugh не уверен, сколько крайних случаев обсуждала команда, так что могу ли я привести здесь несколько для уточнения? Я перечислил несколько примеров ниже и прокомментировал каждый из них тем, что, по моему мнению, должно быть результатом операции typeof , с вопросительными знаками в сомнительных случаях.


Обозначение индексатора
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?

Обозначение типа возвращаемого значения функции
// 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


Извлечение части класса/типа интерфейса

То есть приведенные выше примеры typeof (<MyInterface> null).prop1.big.complex; .

Я понимаю из комментариев выше, что это выходит за рамки и не будет поддерживаться. Это правильно?

Обозначение индексатора
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

Обозначение типа возвращаемого значения функции
// 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

Мне интересно, что здесь происходит:

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

@SaschaNaz хороший вопрос. Аналогичная ситуация:

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

В этом случае имеет смысл в typeof f(MyClass) , чтобы _type_ MyClass рассматривался перед _value_ MyClass (т. е. функцией-конструктором). Первое приводит к Ret = number , второе — к чему-то вроде error: MyClass is not a type .

Будет ли та же логика применяться к имени, которое ссылается как на _type_, так и на _const value_? В вашем примере это будет означать, что тип number всегда будет иметь приоритет над константным значением number . Есть мысли @RyanCavanaugh?

Правильно, мы решили бы это с помощью обычной семантики выражения типа (как если бы вы написали var x: [whatever] ). Таким образом, вы могли бы иметь typeof f(MyClass) , ссылаясь на вызов f со стороны экземпляра, и typeof f(typeof MyClass) , ссылаясь на вызов f с функцией конструктора.

Итак, пример @SaschaNaz недвусмысленно относится к number как к _type_, а не как _const value_, верно?

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

@RyanCavanaugh , можете ли вы подтвердить, что третья группа вариантов использования выходит за рамки? например из ОП:

// 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: {}}}
}

Этот вариант использования (с любым синтаксисом) в настоящее время поддерживаться не будет, верно?

Я думаю, что это должно быть покрыто, разрешая выражения this .

   prop2: typeof this.prop1.big.complex;

Я думаю, что проблема с const должна быть решена с помощью другого typeof .

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

... в то время как это заблокирует typeof data[LOBOUND] .

@mhegazy это отличная идея насчет typeof this . Я только что понял, что это уже работает в разветвленной реализации, которую я сделал для этого предложения. Ну, это работает для классов. Для интерфейсов ошибки нет, но тип this всегда выводится как any . Текущий вывод из разветвленного внедрения:

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
}

Возможен ли вывод this внутри объявлений интерфейса или эта функция останется ограниченной классами?

Я хочу сделать два замечания по поводу #6179 и 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. Количество параметров может быть большим. Допустим, 15 параметров. При этом перегрузок нет, и только для перегрузок нужны параметры в typeof . Итак, для этого случая, можем ли мы, вероятно, придумать синтаксис, подобный следующему?

    type MyAPI = typeof myAPIFactory(...);
    
  2. Фабричная функция обычно не назначается собственной глобальной переменной. Используется функциональное выражение:

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

    Вот как это обычно выглядит. Как видно, typeof здесь вообще нельзя использовать.

@ thron0 моя интуиция подсказывает, что вещь, которая имеет более нескольких параметров, но _также_ возвращает анонимный тип, будет очень редкой. Как вы думаете?

Он станет редким, когда Angular 1 станет редким, а не раньше.

По сути, то, что вы описали, - это типичная Angular _factory_:

вещь, которая имеет более чем несколько параметров, но также возвращает анонимный тип

Это функция, которая принимает зависимости в качестве параметров и создает экземпляр _service_. Вы можете подумать, что большое количество параметров может быть проблемой, но дело в том, что эти фабрики никогда не вызываются напрямую. Контейнер внедрения зависимостей вызывает их. Фабрика вызывается, когда что-то требует своего возвращаемого значения (сервиса) в качестве зависимости. И он вызывается только один раз, так как сервисы всегда являются синглтонами в Angular. Однако сервис может быть чем угодно, поэтому, если нам нужно неодноэлементное поведение, фабрика может вернуть конструктор (или фабричную функцию). Так же, как в этом примере кода:

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

Вот как выглядит мой текущий обходной путь для этой ситуации:

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');
    /* ... */
});

Как видите, это совершенно уродливо и болезненно. Я должен перечислить зависимости три раза. Если у меня больше зависимостей, то иногда проще написать класс обычным способом (внутри фабричной функции) и объявить интерфейс для его потребителей.

Привет, я хочу предложить оставить нетронутым синтаксис/семантику typeof и вместо этого реализовать простую алгебру доступа к типам:

Давайте представим расширение синтаксиса типа:

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

Каждый идентификатор в области может быть одновременно привязан к двум объектам:

  • тип времени компиляции
  • значение времени выполнения

защита типа извлекает только первое. Таким образом

class A {}
var a: A;

эквивалентно

class A {}
var a: type A;

в этом случае, если бы у нас был класс

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

тогда мы могли бы написать

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

Он также охватывает слияние сущностей _type_ и _value_ под одним и тем же идентификатором в лексической области видимости.

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 , можешь рассмотреть эту идею?

Как вы фиксируете тип выражения в своем предложении?

Чт, 19 мая 2016 г., 12:26 Анатолий Ресин, уведомления@github.com написал:

Привет, я хочу предложить оставить нетронутым синтаксис/семантику typeof и
вместо этого реализуйте простую алгебру доступа к типам:

Давайте представим расширение синтаксиса типа:

тип ::= ... | буквальный тип | тип typeAccess | охраняемый
охраняемый ::= идентификатор "типа"
literalType ::= stringLiteral | номерлитерал | симлитерал | booleanLiteral
typeAccess ::= typeField | типПодстрочный | типВызов
поле типа ::= "." я бы
typeSubscript ::= "[" тип "]"
typeCall ::= "(" [тип {"," тип}] ")"

Каждый идентификатор в области может быть одновременно привязан к двум
сущности:

  • тип времени компиляции
  • значение времени выполнения

защита _type_ извлекает только первую. Таким образом

класс А {}
а: а

эквивалентно

класс А {}
а: тип А

в этом случае, если бы у нас был класс

класс АВС {
число;
б(х: число): строка;
с: строка [];
д:[число, строка];
}

тогда мы могли бы написать

var a: (тип ABC).a; // числовая переменная b: (тип ABC).b(число); // stringvar c: (type ABC).c[number]; // stringvar d: (тип ABC).c[0]; // количество

Он также охватывает слияние сущностей _type_ и сущностей _value_ под
тот же идентификатор в лексической области видимости.

интерфейс ЖЕ {
х: число;
}пространство имен ЖЕ: {
тип экспорта x = логический;
экспортная переменная x: строка;
}
var a: SAME.x // логическая переменная b: (тип SAME).x // числовая переменная b: (тип SAME).x // строка

@RyanCavanaugh https://github.com/RyanCavanaugh , @sandersn
https://github.com/sandersn вы можете изучить эту идею?


Вы получаете это, потому что подписаны на эту тему.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/6606#issuecomment-220378019

Ваш пример напоминает мне о том, что тип выражения с точкой и тип свойства типа #1295 - очень похожее предложение. За исключением того, что один более статичен, другой более динамичен.

Мне нравится идея пропустить ключевое слово typeof.

   prop2: this.prop1.big.complex;

@mhegazy в вашем примере вы используете typeof в this-expression :

prop2: typeof this.prop1.big.complex;

Поскольку мы можем сделать это сегодня:

someProp: this;

На мой взгляд, экстраполяция приведенного выше синтаксиса для более точечных свойств:

someProp: this.prop1.prop2.prop3;

И не:

someProp: typeof this.prop1.prop2.prop3;

И чтобы просто продолжить экстраполяцию — почему бы вообще не пропустить typeof?

someProps: foo(number)

почему бы не пропустить typeof все вместе?

Одна из причин, о которой я могу думать, заключается в том, что может возникнуть двусмысленность в отношении того, ссылаетесь ли вы на сторону экземпляра класса или на статическую сторону:

class C {}

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

Я также хотел бы ссылаться на тип свойства в интерфейсе внутри другого интерфейса. Опубликовал мой пример как # 10588, который теперь закрыт в пользу этого тикета.

Было бы неплохо написать что-то вроде этого:

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

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

что бы просто перевести на это:

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<string>;
}

Что ж, некоторые из ранее упомянутых сценариев теперь могут быть выражены с помощью _индексированных типов доступа_ (#11929). Просто объедините эту функцию со стандартным выражением typeof , и вы можете получить что-то вроде этого:

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

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

и это работает! (в настоящее время в typescript@next)

Я думаю, что следующим естественным было бы иметь что-то подобное, но для функций, некоторые "типы вызовов функций", например:

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

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

поэтому мы могли бы естественным образом комбинировать его с оператором 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}

И похоже, что это синтаксис, который уже был предложен в начале этой темы (@tinganho). :)
Но это была бы более общая функция, которая просто красиво сочеталась бы с текущим оператором typeof , точно так же, как сейчас делают _индексированные типы доступа_.

Однако некоторые вопросы/сомнения все еще остаются:

  • Будет ли эта функция обрабатывать некоторые другие сценарии, такие как _индексированные типы доступа_? Если нет, то стоит ли его реализовывать?
  • Я предполагаю, что в большинстве случаев было бы неплохо вообще не указывать типы аргументов, поскольку наиболее распространенным сценарием является «Я просто хочу получить тип этой одной функции, которая не имеет других перегрузок». для этого подойдет новый конкретный оператор (например, typeofreturn или returnof )

Кажется, это технически осуществимо. Может потребоваться рефакторинг разрешения перегрузки/вывода аргумента типа, чтобы вам не нужно было разрешать аргументы. Я не думаю, что дженерики создают слишком много дополнительной проблемы. Один вопрос, который следует задать, заключается в том, что произойдет, если ни одна перегрузка не соответствует вызову.

Я думаю, что не очевидно, желательна ли эта функция. Если функция возвращает очень сложный анонимный тип, не будет ли полезнее для автора указать имя типа, чтобы на него не нужно было ссылаться таким образом? Я признаю, что это просто основано на стилистических предпочтениях. Я полагаю, я не уверен, насколько удобно это будет использовать на практике. Я думаю, что это нормально для доступа к элементам.

тип P = тип foo (0); // P любое[]
тип Q = typeof foo(true); // Q является строкой

Что касается этого обсуждения разрешения ссылаться на параметры по их типам, я бы предложил разрешить то же самое и для реальных функций.
Контекст: я хотел бы ввести функцию map над объектами:
function map<T, F extends Function>(fn: F, obj: T): { [P in keyof T]: typeof F(T[P]) }
Причина, по которой я хотел бы иметь возможность ссылаться на функцию по ее типу F (помимо только ее имени fn ), связана со случаями, когда ее имя может быть недоступно, например, я' хотел бы иметь возможность сказать type MapFn<T, F extends Function> = { [P in keyof T]: typeof F(T[P]) } .

Что-то, что, вероятно, было бы намного проще реализовать (и все же очень полезно):

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

Теоретически, они также могут быть объединены в цепочку, если вы хотите получить шрифт на несколько уровней глубже. Есть предположения?

Редактировать: только что понял, что это было упомянуто выше @mpawelski 😄

@dehli Это не сработает для перегруженных функций. Или универсальные функции, в которых параметр типа используется в возвращаемом типе.

@JsonFreeman Не могли бы перегружать функции всего OR различными типами возвращаемых значений? И общие функции могут потребовать от вас указать тип?

Это было бы возможно, но я не уверен, насколько это будет полезно. Кажется, что люди хотят чего-то более сложного.

Я действительно надеюсь, что это скоро будет реализовано, потому что это настоящая боль.

Обходной путь

Поскольку последний обходной путь больше не работает, я сейчас использую это:

// 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;

// ...

Вам не нужно указывать аргументы, если ваша функция не перегружена.

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

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

Перегруженные функции

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

Все определения здесь:
https://github.com/kube/returnof/blob/master/lib/returnof.d.ts

Я только что создал небольшой пакет NPM, чтобы легко связать его, не загрязняя ваш проект.

Если добавить необязательные универсальные шаблоны, как это предлагается в https://github.com/Microsoft/TypeScript/issues/2175 , это позволит использовать простой декларативный обходной путь:

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

И используйте это так:

type helloReturnType = Return<typeof hello>

@mhegazy @JsonFreeman Есть планы по этой функции?

Всем привет,
У меня есть альтернативное возможное решение этой проблемы (извините, если оно уже было предложено, и я его пропустил) - я предложил его в выпуске #13949. На том этапе я не знал об операторе typeof, но думаю, что мое решение более общее. По сути, введите новый синтаксис, что-то вроде =MyType , который можно использовать везде, где бы вы использовали MyType , но вместо объявления объекта как типа MyType он создает тип с именем MyType из предполагаемого типа объекта. Например, вот как я бы использовал его при создании компонента 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...

Обратите внимание, что функция createData также может быть написана

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

Я нашел действительно забавную работу, которая обобщает любое выражение:

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

РЕДАКТИРОВАТЬ: Хватит смеяться надо мной, ЭТО СЕРЬЕЗНО МАШИНОСТРОНИК

@johnfn это выглядит забавно для простых случаев :), но если вашей функции требуются некоторые параметры, вам придется выполнить дополнительную работу, например:

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

Я использую другой обходной путь, который особенно полезен для React и Redux, когда я пытаюсь использовать вывод типа для получения типа реквизита из функции mapStateToProps . Тогда больше не нужно вручную объявлять и поддерживать интерфейс реквизитов, введенных Redux connect , который утомительно поддерживать и подвержен ошибкам.
Этот обходной путь также будет хорошо обрабатывать любую заданную сигнатуру функции для других вариантов использования.

Пример использования оператора typeof "React & Redux":

В приведенном ниже примере попытайтесь показать общий вариант использования в реальном проекте для использования оператора typeof для получения типа из объявления функции, который был бы чрезвычайно полезен, если бы был добавлен в 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> {
...

Источник: https://github.com/piotrwitek/react-redux-typescript-patterns#react-connected-components

Решение @kube и их пакет https://github.com/kube/returnof работают, как и ожидалось! 👍

Хм. Я бы хотел, чтобы returnof помогал набирать map() на основе кортежа из файла .d.ts , но, учитывая его зависимость от языка выражений ( const нельзя использовать в окружающих контекстах ), я боюсь, что мой вариант использования будет по крайней мере более сложным.

Редактировать: кажется, что необязательные дженерики уже объединены , что означает, что версия @kube только для языка декларативного типа ( type Return<T extends () => S, S> = S -> type helloReturnType = Return<typeof hello> ) станет жизнеспособной. :D

Исправление: нет, объединенная поддержка универсальных значений по умолчанию, по-видимому, не позволяет указывать часть универсальных значений, позволяя другим вернуться к их предполагаемым значениям; Return<typeof hello> просто выдает ошибку Generic type 'Return' requires 2 type argument(s). .

@ tycho01 Да, я был разочарован тем, что это не решило проблему.

Я уже открывал связанную проблему с выводом необязательных общих типов :

14400

Обычно вы можете получить возвращаемый тип функции с помощью комбинации фиктивной внешней функции и вывода типа:

окружающая среда function returnTypeOf<RT>(fn:(...rest:any[])=>RT):RT {return void 0};
локально var r = returnTypeOf(someFunction); получает значение undefined

Но если вы затем захотите повторно использовать этот возвращаемый тип, вы должны его зафиксировать... так что мы вернулись к тому, с чего начали:

type RT = typeof r;

Было бы намного проще, если бы мы просто изначально расширили концепцию typeof , чтобы разрешить returntypeof или, что еще лучше, вывести это использование из typeof fn(a,b,c,...) , что могло бы затем фиксировать различные типы возврата подписи. . Это понимание возвращаемого типа уже выполняется внутри TS.

typeofexpression () будет своего рода рекурсией возвращаемого типа, смешанной с операциями типа: например

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

является

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

который может быть понят как один из

type E = typecomprehensionof (string + string) т.е. string
type E = typecomprehensionof (string + number) т.е. string
type E = typecomprehensionof (number + number) т.е. number

Насколько это сложно внутренне и затраты на исполнение мне неизвестны.

РЕДАКТИРОВАТЬ ------------------------------------------------- ------------

Я хотел добавить... это особенно важно для всех, кто должен использовать Function.bind, Function.apply, Function.call, потому что в настоящее время они возвращают тип any , а это означает, что они должны постоянно быть типизированными. аннотированы, чтобы гарантировать, что они не выпадают из процесса проверки типов.

Возможность ссылаться на возвращаемый тип аргумента функции была бы... блаженством...

@poseidoncore

Возможность ссылаться на возвращаемый тип аргумента функции была бы... блаженством...

Мне больше нравится текущий typeof <expression> , а проблема с типом возвращаемого значения уже рассмотрена в #12342.

Я просто использовал typeofexpression и typecomprehensionof как средство различения этих вариантов использования в объяснении. Я бы предпочел typeof (expression) в качестве фактического синтаксиса.

Я хотел сказать, что понимание типа выражения потребует понимания возвращаемого типа функции... f(1) также является выражением. #12342 связан таким образом. Они не исключают друг друга.

Мы уже можем сделать typeof для переменных, и поэтому, поскольку выражение является операцией над переменными и функциями, следующим требованием является возможность вернуть тип функции... и затем понять результат на основе правил типов. .

На самом деле, хороший момент. Проблема № 12342 заключается в том, чтобы получить доступ к возвращаемому типу как к свойству для универсальных типов, поэтому я неправильно понял отношение.

Как насчет использования выражений для вызова функции typeof , как в предложении, но с использованием типов непосредственно вместо параметров?

Например

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}

Обратите внимание, что это не мешает никому использовать типы переменных с локальной областью видимости, используя typeof внутри вызовов, т.е.:

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

Это кажется немного лучше, чем исходное предложение, поскольку оно работает в окружающем контексте и в целом кажется более гибким. Для меня это также проясняет, что мы на самом деле не вызываем функцию, а просто пытаемся вернуть тип ее возвращаемого значения.

Как я это сделал для своего простого случая:

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"'.

Вышеприведенное также работает с членами самого интерфейса:

declare let myIface: MyInterface;

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

Преимущество использования declare let ... вместо type ... для меня заключается в том, что он ничего не создает в выходном файле .js.

@varadero это не решает проблему, связанную с получением возвращаемого типа вызова функции. Все, что вы делаете, это извлекаете тип свойства типа, что, как мне кажется, поддерживается начиная с TypeScript 2.2.

На самом деле это тип свойства (поддельного) значения. Компилятор не позволяет вам использовать typeof с "чистым" типом или интерфейсом, он работает только со значениями.

Было бы здорово иметь это!

поскольку type KEYOF<T extends any[]> = keyof T[0] уже существует, typeof T[0]('something') также будет работать с этой реализацией, предполагая, что function myFunc<T, K extends KEYOF<T>>(type: K)>{ } и myFunc([(r: string) => r]) (вернет строку для typeof T[0]('something') ) ?

Это было бы мощно вместе с полиморфным this .

Пора этому случиться!

Перечитывая эту ветку, я попытался понять, что мы делаем с синтаксисом typeof и почему.

Очевидно, что в уже реализованном тривиальном случае typeof foo это дает тип foo . (Я предполагаю, что pet is Fish невосприимчив к использованию специального синтаксиса.)

В моем текущем прочтении того, что это ключевое слово должно делать в текущем предложении, само ключевое слово делает не больше того, что оно делает сейчас, и текущее предложение фактически не связано с ключевым словом typeof .

Я говорю об этом, потому что это имеет значение в случае, о котором я упоминал выше, когда мы применяем функцию, хранящуюся как тип (будь то через type или как универсальный), а не как значение.

Учитывая это, я предполагаю, что в то время как typeof fn<A>(string) требуется typeof для поднятия переменной уровня выражения fn для использования на уровне типа, Fn<A>(string) с другой стороны , с Fn в качестве универсального, содержащего функцию, не требует этого и, следовательно, может быть «применен», чтобы получить соответствующий возвращаемый тип здесь без необходимости typeof .

В этой интерпретации мы будем проверять вызовы функций после любых потенциальных типов функций: помимо typeof fn(...) / typeof fn<...>(...) также Fn(...) / Fn<...>(...) , если даже не функция литерал ((foo: Foo) => Bar)(Baz) / + дженерики. В противном случае план атаки должен остаться в силе.

Может быть, я неправильно истолковываю, как вы, ребята, видите это, возможно, функции, хранящиеся в типах, просто даже не рассматривались (поскольку я не нашел о них упоминания). В любом случае, я решил, что стоит подтвердить.

Если объявление о применении функции становится еще одной семантической перегрузкой для typeof , в дополнение к устранению неоднозначности конструкторов классов и поднятию переменных уровня выражения на уровень типа (помимо уровня выражения typeof ), вещи кажутся постепенно более запутанными, как уже указывалось несколькими более ранними вопросами в этой теме.

Редактировать: универсальный тип уже может возвращать тип функции, который также может иметь дженерики. Это означает, что другой перестановкой будет GetFn<A><B>() , где первый общий набор будет принадлежать вызову универсального типа, а последний — вызову функции. Однако нет GetFn<A><B><C>() , хотя GetFn<A><B>()<C>() также будет законным. Однако предыдущий вывод остается неизменным: любой тип может содержать функцию и, следовательно, (потенциально) применяться как функция.

Редактировать 2: я только что понял, что в X<Y>() будет досадная двусмысленность. Теперь X будет типом, возвращающим функциональный тип.

  • Если X не является общим, это ясно — <Y> принадлежит вызову функции.
  • Если X является универсальным и требует параметр, это тоже понятно — он принадлежит типу, и функции не передаются никакие параметры типа.
  • Однако, если X имеет необязательный дженерик, это становится неоднозначным.

    • В одной интерпретации X передается как параметр типа, а функция — нет.

    • В другой интерпретации X остается использовать параметр типа по умолчанию, а <Y> вместо этого будет параметризовать вызов функции.

Я еще не уверен, что было бы лучшим решением здесь.

  • Одним из вариантов было бы заставить вызовы типа полностью полагаться на параметры типа по умолчанию, чтобы явно использовать пустой <> , а не позволять пропускать это. Однако это было бы переломным моментом.
  • В качестве альтернативы можно было бы наложить такое ограничение на вызов функции на уровне типа — поскольку это было бы новым, никаких критических изменений вноситься не будет. Однако такой подход был бы неэлегантным, так как вводил бы асимметрию между синтаксисом вызова функций между языком выражений и языком типов, что может сделать его менее интуитивным.

@icholy : да, я знаю, эти правки просто дополняют твою точку зрения.

Редактировать 3: хорошо, эта функция теперь превосходит верхнюю часть моего списка пожеланий. Никакое другое обновление даже близко не приближается с точки зрения влияния на вывод.

wat

@icholy : короче говоря, если «функция» относится к универсальному типу, будем ли мы по-прежнему писать typeof ?

Если это сделать так, чтобы оно правильно взаимодействовало с перегрузками, оно фактически дает вам «вариативные» функции бесплатно (они просто каррируются, а не имеют арность >1), поскольку вы можете рекурсивно определить возвращаемый тип функции в терминах применение одной из его перегрузок с некоторой базовой перегрузкой. Именно так все делается в Haskell, и было бы замечательно иметь такую ​​возможность в TypeScript.

@masaeedu : звучит интересно. И да, перегрузки определенно делают это предложение таким интересным — их можно использовать для сопоставления с образцом различных вариантов, включая резервные варианты any . Такие проверки типов до сих пор были невозможны на уровне типов.

Я признаю, что использовал Ramda больше, чем Haskell. Но на этом фоне я подумал, что каррирование обычно плохо сочетается с функциями с переменным числом аргументов, так как карри нужно «знать», следует ли возвращать результат или другую функцию для обработки дополнительных аргументов.

Не могли бы вы показать некоторый псевдокод того, как вы увидите, как эта идея работает для функций с переменным числом переменных, таких как Object.assign (пропуская детали, такие как & против Overwrite , как я использовал в моем PoC за R.mergeAll )?

@tycho01 Tycho01 Я играл с таким кодом:

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 является "вариативным" типом функции в том смысле, что вы можете дать ему произвольное количество разнородных аргументов, и он будет точно записывать связанную информацию о типе, используя рекурсию на уровне типа. К сожалению, TypeScript не имеет хорошей поддержки преобразования и сопоставления с образцом на уровне типа, как это делает Haskell.

Если бы у нас был доступ к typeof , я смог бы решить базовую задачу для Pop , поскольку returnof(overloadedFunction(T)) , по сути, дал бы мне способ сделать шаблон- соответствие на уровне типов.

@tycho01 Tycho01 Я не могу сделать это именно для Object.assign , потому что, как я уже сказал, это работает только для каррированных функций, но я попытаюсь создать для вас каррированную функцию assign , которая работает примерно так же.

Я бы хотел, чтобы они немного обобщили алгебру типов, например, чтобы (a: string) => (b: number) => void было просто сахаром для Func<string, Func<number, void>> , а (a: string, b: number) => void было просто сахаром для Arity<number, Arity<string, void>> , или что-то такое. Тогда нам просто потребуются инструменты общего назначения для преобразования типов, чтобы быстро получить множество интересных функций.

Извиняюсь за тройной пост. @tycho01 tycho01 Вот карри, "variadic" 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 : Ха, так что я думаю, это похоже на функцию редуктора :), хотя эта часть могла бы стать сложнее, если бы также были невариативные (?) аргументы.

Мне нравится идея; Я определенно не так много думал об интерфейсах.
Если мы сможем изменить JS для типизации, возможно, я попрошу TC39 просто изменить Object.assign на невариативный mergeAll . :D

Не то чтобы моя итерация на основе приращения, подобная этой, на самом деле работала с функциями на практике до сих пор (# 17086) ... но в любом случае я с вами, это предложение окажет большее влияние, чем любое другое, которое я видел.

@ tycho01 Я думаю, вы вряд ли получите большую поддержку, если попросите превратить функции с переменным числом в функции с фиксированной арностью, поскольку каррированные функции или повторяющиеся приложения требуют затрат времени выполнения, которые JS (или, по крайней мере, существующие механизмы и код JS) вероятно, не очень оптимизирован для. Я думаю, что вместо этого нам нужно, чтобы TypeScript позволял одинаково легко создавать и деконструировать типы для функций арности> 1 рекурсивно. Я предполагаю, что все это возвращается к # 5453.

Другим может быть интересно узнать, что в C++ есть очень похожая функция: decltype(expression)

Эта функция когда-нибудь появится в TypeScript?
Я очень предпочитаю исходную предложенную форму, в которой аргументом является выражение, но намерение команды TS иметь специальный синтаксис с типами в позициях выражения просто все усложняет и задерживает его посадку в язык.
Нам нужно поддерживать эту тему.

Я был бы очень рад, если бы мы смогли реализовать хотя бы часть этого предложения. А именно этот единственный:
type A = typeof anotherVariable . Я мог бы сделать все с помощью одной строки. interface B extends A , <B & A> : B & A и т. д.

Я вижу много вариантов использования подобных вещей с React. Когда вы создаете компонент более высокого порядка, гораздо сложнее намекнуть объявлению класса компонента, что в настоящее время он является HOC.

В итоге я создал два класса. Один класс расширяет React.Component и добавляет все дополнительные методы, в то время как класс внутри функции HOC манипулирует render() .

@blindbox type A = typeof anotherVariable уже работает:

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 Вау, ты прав.

Хорошо, я понял, почему это не сработало с моей стороны.
это не сработает

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.

Однако это сработает.

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!

Да, проблема в том, что его нельзя использовать в определенных случаях использования, например, когда вам нужно использовать аргументы универсального типа.

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)) { ... }

@Игорбек :

Я очень предпочитаю исходную предложенную форму, в которой аргументом является выражение, но намерение команды TS иметь специальный синтаксис с типами в позициях выражения просто все усложняет и задерживает его посадку в язык.

Итак, с тех пор, как они это написали, мы получили литералы для строк и чисел, что означает, что все становится немного более нечетким — это будет работать в любом случае. Литеральный массив и объекты также похожи на их обозначения типов, так что пока все хорошо.

Итак, у нас также есть сохраненные значения/типы, например, переменные, дженерики, другие типы. Чтобы вещи стали полезными, мы не должны просто довольствоваться их частью здесь.
Что является хорошим аргументом в пользу их подхода, основанного на типах, так это то, что разрешение этой смеси происходит бесплатно на уровне типов. Другими словами, на уровне типа мы уже могли бы сказать typeof myVar , что означает, что если эта функция «приложение» будет добавлена ​​на уровень типа, мы автоматически сможем подключать как хранимые типы, так и обычные переменные. .

Мне было бы интересно увидеть ваши дальнейшие мысли о первоначально предложенном подходе. По общему признанию, это может помочь раскрыть немного больше на уровне типов: операторы на основе JS (например, ! && || + - * / instanceof ), а также специальные операторы TypeScript (оператор утверждения ! ).
Дело в том, что эти JS-операторы похожи... они довольно бесполезны на уровне типа в его нынешнем виде, так как разрешение им работать с литералами для получения соответствующих литеральных типов результатов в настоящее время считается выходящим за рамки ( ref ) -- выражение -level 1 + 1 просто дает тип number и аналогично для других.
Имея это в виду, я как бы сам отказался от их предложения, основанного на типах.

Эта функция когда-нибудь появится в TypeScript? [...] Нам нужно поддерживать эту тему.

Я предложил это предложение как более общее решение меньшего симптома здесь , хотя и с ограниченным успехом.

@tycho01 Мои аргументы в пользу вариации typeof на основе выражений почти такие же, как изначально заявлено @yortus :

  • согласованность : существующий typeof (в позиции типа) уже работает с выражениями, однако ограничен приемом одного символа. Таким образом, принятие типов или псевдовызовов привело бы к гораздо более громоздкому синтаксису, который представляет собой еще одну ветвь синтаксиса (в дополнение к выражениям и типам).

    • пользователям не нужно изучать новый синтаксис

  • простота : TypeScript, кажется, уже имеет все средства, необходимые для реализации этой функции ненавязчивым способом (это было доказано PR)
  • полнота : выражения, по крайней мере, имеют ту же выразительность, что и типы, поскольку мы всегда можем использовать утверждения типов ( (undefined as any as <any arbitrary type can be here>) ), но иногда в системе типов отсутствуют типы, которые, однако, могут быть выражены в выражениях (типы распространения и остатка были введены после соответствующие выражения были высажены).

Спасибо, @tycho01 , за то, что вы довели эту точку до promised PR (я действительно пришел сюда после ваших комментариев), что действительно показывает, как такая простая и общая функция может охватывать гораздо более сложные сценарии очень элегантным способом без выпечки. в очень специфическом поведении в языке.

Я вижу расширенный typeof как реальный переломный момент для выразительности системы типов, как это сделали сопоставленные типы/ keyof .

@Igorbek : Спасибо за уточнение, я понимаю, откуда вы пришли. Возможно, эти два подхода служат разным вариантам использования.

Я думаю, что вы лучше демонстрируете сегодняшнюю ценность исходного предложения, чем исходный пост сегодня, поскольку текущий TS (или, ну, 2.3.3 Playground ) уже может заставить работать многие из исходных сценариев:

// 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.

Я предполагаю, что эти примеры больше не придают большого значения ни выражениям, ни подходам типов - большинство из них устарели, и применение тривиальных функций было бы разрешено любым из них.

По общему признанию, приложение функции на основе уровня типа, на котором сосредоточились tinganho, команда TS и я, могут предоставлять переменные выражения через typeof , хотя, как вы заметили, уровень типа, по общему признанию, имеет тенденцию отставать от функциональности, представленной в выражении. уровень. Само это функциональное приложение, а также синтаксис распространения, о котором вы упомянули, очевидно, являются основными примерами, и я определенно хотел бы, чтобы они были рассмотрены. Возможно, большую часть текущего разрыва можно было бы даже решить здесь.

Точно так же подход, основанный на выражении, также позволяет вводить типы, используя <MyType> whatever или whatever as MyType , хотя, как и typeof в подходе с типом, это похоже на запоздалую мысль: вопросы о применении функций, хранящихся в типах/дженериках, остается, что, вероятно, компенсирует большую часть фактической добавленной стоимости этого подхода на основе типов (хотя и не упоминается там) - точный вывод для функций более высокого порядка, а также, скажем, условные выражения на основе типов как в этом потоке promised .* Что еще хуже, в отличие от варианта использования OP, у них нет обходных путей.

Я думаю, видите, где идеи противоречат друг другу - текущие предложения имеют противоречивые взгляды на то, изменит ли ключевое слово typeof свое выражение на уровень значения или (в моей интерпретации) оставить typeof как есть. , но раскрывая синтаксис приложения функции на уровне типа.

Хотя, на мой взгляд, это противоречие несколько случайно. Я бы не стал игнорировать законность любого варианта использования; если бы оба были реализованы, я мог бы увидеть дополнительное ключевое слово, позволяющее избежать конфликта семантики. Честно говоря, мне все равно, будут ли ключевые слова так или иначе заканчиваться — я просто хочу печатать дерьмо.

*: Я только что понял, что вы, вероятно, будете использовать функции более высокого порядка, ссылаясь на функции в параметрах по их именам параметров, а не захватывая их типы в дженериках.
Похоже, что на самом деле вещи могут быть преобразованы в обоих направлениях: typeof $ помогает подняться с уровня значения на уровень типа, а declare let y: x; помогает поднять вещи с уровня значения на уровень типа. Неэлегантно, как обходной путь OP, но да.
Если бы типы функций были введены, например, через явные дженерики, я думаю, это не помогло бы - они были бы на уровне типов без возможности их перемещения.
Если это звучит так, как будто я заранее надеюсь охватить основы функциональности, это, вероятно, потому, что большая часть моего прогресса в нерешенных задачах набора текста была заблокирована в этом дополнении (с заметным упоминанием 5453). Но если мы сможем разобраться в этих вещах, это того стоит.

Изменить: я подумал о нескольких теоретических случаях, которые теперь могут быть исключены в подходе, основанном на выражениях, хотя на ум пока не приходят практические случаи:

  • функции, переданные через явно предоставленный универсальный.
  • функции, возвращаемые параметрическими функциями, должны быть захвачены в обобщенном виде, т. е. (используя синтаксис предложения на основе выражений) 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 . Конечно, их можно было бы рассчитать, если бы вы знали и могли предоставить типы параметров G , но, на самом деле, пока нет способа получить эту информацию (разве что #14400?). Я предполагаю, что эта категория будет включать функции, работающие с фабричными функциями, используемыми в AngularJS, упомянутыми выше.

@Igorbek Я не понимаю, чего вы ожидаете от этого фрагмента:

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

Это похоже на круговое определение.

@masaeedu извините, я имел в виду:

(переключитесь на f вместо someFunctionWithComplexReturnTypeThatUsesGenerics )

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

Просто чтобы уточнить, это то, что в настоящее время нельзя обойти, введя фальшивую переменную:

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

Кстати, только что понял, что текущее предложение конфликтует с оператором типа запроса типа члена T[K] , поскольку typeof имеет более высокий приоритет:

  • typeof x[K] настоящее время означает (typeof x)[K] , где K _является_ типом
  • если берется вариация на основе выражений, будет введена двусмысленность:

    • typeof x[k] означает (typeof x)[k] , где k — тип; или

    • typeof x[k] означает typeof (x[k]) , где k — выражение

На самом деле я бы предпочел typeof (x[k]) , потому что они семантически эквивалентны, но это определенно критическое изменение.

Когда я использовал инструменты разработчика Chrome, я понял, что оператор-член имеет более высокий приоритет. Где вы нашли это?

image

@Igorbek : да, похоже, именно поэтому type Thing2Type = typeof things['thing-2']; из исходного поста уже работает.

@dehli : вы сравниваете уровень выражения JS с уровнем типа TS - они не совпадают. Один работает в браузерах/узле, другой в компиляторе TS.

Мне лично кажется странным , что TypeScript разрешает уровень типа typeof с более высоким приоритетом, чем доступ к индексированному типу. Честно говоря, мне это кажется почти ошибкой. (Интересно, действительно ли этот случай проверен.)

@tycho01 Попался. Я предполагал, что TS будет использовать тот же порядок старшинства, что и JS.

@dehli уровни выражения такие же, да. На уровне типа это другая вещь с похожим синтаксисом, но возвращающая тип вместо строки.

Я предполагаю, что приоритет может быть результатом ограничений, которые они предполагали для него. Я допускаю, что эти соображения могут больше не казаться столь же разумными, если его функция должна быть расширена.

С другой стороны, если бы обе версии предложения были реализованы, скобки послужили бы эффективным способом четко указать, на каком уровне мы находимся и в этом случае. У меня проблемы с придумыванием вещей, которые не звучат как компромиссы, но да.

@Igorbek Является ли это основным оставшимся вариантом использования typeof в произвольных выражениях? Если мы получим #14400, это даст нам returnof с использованием простых пользовательских типов, если я не ошибаюсь.

14400 не даст нам returnof в общем виде:

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'

Я не в курсе всех оставшихся вариантов использования, они, кажется, еще не обнаружены, но самые непревзойденные и привлекательные для меня:

  • _типы с условным отображением_, которые намного мощнее, чем мы могли бы получить с #12424, потому что typeof будет использовать стандартное разрешение перегрузки; @tycho01 показал элегантный пример в типах promised PR #17077.
  • получение типов возвращаемых функций/методов в контексте универсальных типов (как я показал ранее)

@masaeedu : Хороший вопрос. Краткая версия - это почти то, что сказал @Igorbek ; для более подробной информации о конкретных решенных проблемах вы найдете краткий список в моем списке «основных необходимых функций» в № 16392, где я попытался связать нерешенные проблемы с предложениями, которые могли бы их реализовать.

Это включает в себя:

  • фабричные функции, такие как используемые, например, AngularJS, см. три темы, связанные в исходном сообщении здесь. - на самом деле это должно быть хорошо, учитывая недавно встроенный ReturnType .
  • Имея известные входные данные, вы можете внезапно вычислить точные типы возвращаемых значений для таких вещей, как reduce / map / filter / find — все, что связано с итерацией и проверками. Там выиграют stdlib.d.ts , как и библиотеки FP, такие как Ramda/Lodash. На практике не все входные данные могут быть известны (массивы больше похожи на списки, чем кортежи), но операции map и filter над объектами могут быть типизированы лучше, чем просто Partial . Это необходимо для улучшения типов библиотек управления состоянием, таких как redux (см. https://github.com/piotrwitek/react-redux-typescript/issues/1) и Angular ngrx, которые до этого вынуждены поддерживать низкоуровневый пользовательский API. потому что без хорошо типизированной операции map у них нет возможности вычислить желаемый результат на уровне типа из входных данных DRY'er.
  • В настоящее время типизация композиции функций также не может учитывать типы возвращаемых данных, зависящие от ввода.
  • Это также внезапно сделало бы возможным начать печатать такие вещи, как линзы. - Я полагаю, что технически это все еще заблокировано здесь только для операций редактирования с типами возврата, зависящими от ввода. что на самом деле является роскошью. однако для изменения кортежей по-прежнему требуется (часть) # 5453.
    Это также позволяет:
  • работа с булевыми литералами на уровне типа, что до сих пор было невозможно
  • типы развертывания для операций, подобных flatMap , таких как предложение promised
  • добавление ограничений для ввода типа/функции, тема #15347 и идея, которую я почерпнул из книги «Разработка, управляемая типами, с Idris» о зависимых типах. Это может помочь запретить делители 0 — по сути, хак для вычитания из типов, тема № 4183. Я уверен, что еще не представил себе большинство вариантов использования, но это можно сделать с помощью этого, например, function div<B extends number, NotZero = { (v: '1') => 'whatever'; }({ (v: 0) => '0'; (v: number) => '1'; }(B))>(a: number, b: B) . Обычно язык Idris рекламирует с безопасными по длине векторными/матричными операциями, но это необходимо только для функций более высокого порядка.
  • Способ выражения ограничений Union<T> / NonUnion<T> для входных типов с использованием приведенных выше ограничений + IsUnion . - этот тип сломался, и я не уверен, как это сделать.
  • учитывая # 5453 , это также помогает набирать такие функции, как curry , Function.prototype.bind , Promise.all (не исчерпывающий список, просто некоторые функции, которые другие люди назвали сложными).
  • Логика switch , предложенная в #13500, здесь решена путем сопоставления шаблонов перегрузки функций
  • те сопоставленные условные типы, которые являются темой # 12424 и # 17077 ( promised ), также известные как проверки типов на уровне типов. Однако это не решило бы проблему — даже если бы сегодня это можно было использовать для создания логических литералов где угодно, до тех пор, пока не появится этот # 6606, у нас все еще не было возможности оперировать/выполнять условные выражения, основанные на таких логических литералах на уровне типа. в любом случае.
  • проверка наличия строкового индекса для типов объектов, например, для сохранения этого между операциями (например, Omit , Overwrite из #12215), см. мой комментарий в #12424
  • Основываясь на приведенных выше проверках строкового индекса, способ исправить доступ к свойствам типа объекта таким образом, чтобы проверки строк, совпадающих с именами методов прототипа объекта (например, toString , toLocaleString ), разрешались в строковый индекс, а не чем методы-прототипы, потенциально исправляя сбои, связанные с доступом к объекту toString во всех других операциях с типами, которые ранее зависели от сбойного доступа к встроенному свойству объекта.

edit: вычеркнуты варианты использования, заполненные условными типами (#21496) с момента написания.

Предложение @Igorbek о подходе, ориентированном на выражение, было бы (возможно, за счет нарушения некоторых неизвестных приложений части приведенного выше списка - мне еще предстоит придумать что-то конкретное, хотя я боюсь быть загнанным в угол ) дополнительно обещают сократить разрыв между уровнями значений и типов, которые в настоящее время включают в себя такие вещи, как приложение функции (это # ​​6606), распространение/остаток (# 5453), оператор утверждения ! ( # 17370), тип объединения вычитание через защиту типа (#4183, в противном случае достижимое с помощью ограничений, упомянутых выше) и, возможно, многое другое, о чем я не могу придумать.

Я предполагаю, что весь этот компромисс может вызвать некоторые вопросы, например, зачем делать одну и ту же функциональность доступной двумя способами (фактический уровень типа, доступ к уровню выражения, встроенному в уровень типа).

Изменить: обновил мой сравнительный комментарий выше, указав дополнительную потенциальную проблему.

Редактировать 2:

К сожалению, исходный пост заставил вас поверить, что это позволит писать только некоторые крайние случаи без какого-либо обходного пути null & , в то время как на самом деле это критическая функция, сдерживающая TS прямо сейчас без известных обходных путей для окружающих контекстов. .

Это означает, что это влияет на TS, написанные в отдельных файлах .d.ts , что является нормой не только для stdlib.d.ts , но и для всех типизаций DT, написанных отдельно от их исходных JS-проектов, которые вряд ли изменятся. в то время как библиотеки JS хотят оставаться открытыми для таких альтернатив, как Flow.

(Это не намного лучше для типов .ts , поскольку типы, использующие «обходной путь» OP, не могут быть параметризованы, например, для объединения в повторно используемые типы более высокого уровня без влияния на уровень выражения.)

@tycho01 Спасибо за список; там много ссылок, которые мне нужно переварить. Я действительно хочу сопоставление с образцом для типов, и #6606 — хорошее, прагматичное решение проблемы. Однако мне кажется, что путаница между вещами на уровне значений и типов увеличивается, и #6606 не улучшит ситуацию в этой области.

Причина, по которой эта функция необходима, заключается в том, что у нас нет возможности создавать выражения типа, соответствующие:

  • возвращаемый тип функционального типа при применении с аргументами определенных типов
  • (до typeof K[L] ) тип свойства в типе объекта, который соответствует ключу типа строкового литерала
  • (возможно и другие, мне нужно более внимательно просмотреть ваш список)

Я чувствую, что на самом деле должна быть возможность создавать для них чисто выражения уровня типа, не прибегая к смешиванию конструкций уровня значения и выражений уровня типа. Было бы неплохо, если бы такие типы, как (a: A) => B или A | B , были сахаром для простых параметризованных типов, таких как Func<A, B> , Union<A, B> , и у нас были общие инструменты для манипулирования параметризованными типами. (HKTs, fundeps или типовые семейства и т. д.).

Я знаю, что чрезмерное внимание к надежности не является одной из целей TypeScript, но сейчас существует множество концепций на уровне типов, которые непрозрачным образом взаимодействуют друг с другом. Некоторая формализация того, что такое тип, и способ предписания механических правил для того, как тип взаимодействует с другими типами (подтипирование, деструктурирование и т. д.), имели бы большое значение.

Я чувствую, что на самом деле должна быть возможность создавать для них чисто выражения уровня типа, не прибегая к смешиванию конструкций уровня значения и выражений уровня типа.

О, да, лично я вообще не собирался прибегать к конструкциям уровня значений — это все, что я пытаюсь сделать. Если бы уровень значения был необходим, я бы сам был в лагере, основанном на выражениях. :П

Я предполагаю, что разрыв между уровнями значений и типов, как ожидается, сократится (движется ли TC39 быстрее, чем TS?), и, на самом деле, разрывы уже имеют выдающееся предложение по уровням типов (см. нижнюю часть моего предыдущего поста).

Черт возьми, я понимаю, что создание типов для некоторых функций будет за пределами опыта большинства пользователей.
Я смотрю на это так: я хочу, чтобы стандартная библиотека и библиотеки FP были типизированы настолько хорошо, чтобы пользователи TS могли просто написать их и получить для них автоматический вывод.
В TS есть только несколько операторов уровня типа, но решение реальных проблем с ними (яркий пример — Overwrite / Omit #12215 ) вполне может показаться случайным веб-разработчикам несложной задачей. Черт возьми, это тоже до недавнего времени, и они еще даже не защищены от прототипа/индекса/символа.

Было бы неплохо, если бы такие типы, как (a: A) => B или A | B были сахаром для простых параметризованных типов, таких как Func , Union

Мы можем перевернуть его и создать параметризованные типы как псевдонимы/конструкторы типов. Для операции с типом, принимающей Foo<Bar> , не имеет значения, упрощается ли ваша вещь до единицы — она просто проверяет, соответствует ли она описанию.
Это в значительной степени то, что делает stdlib.d.ts - у вас есть foo[] , но он удовлетворяет описанию Array<Foo> и, следовательно, работает с типизацией Array.prototype .
Хотя на самом деле это не помогает:

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>;

Так что да, не решил эту проблему, но только что понял, что # 14400 kube действительно может помочь. И я только что видел вас там сегодня утром!
На самом деле то же самое для функций - что является хорошим напоминанием о том, что # 14400 может не только возвращать типы функций, но и типы параметров.

Этот подход на данный момент потребует использования перегрузок, что досадно, поскольку он не масштабируется, но да. Чтобы снова сделать его универсальным, мы, по сути, использовали бы эти перегрузки сопоставления с образцом #6606, чтобы активировать правильный вариант для разных типов.
Я полагаю, что это можно использовать для общего преобразования их в типы кортежей, а затем перебирать их, используя мой подход с приращением , чтобы каким-то образом работать с ними.
Для союзов я надеялся на более красивый способ преобразования в типы кортежей. Однако это добавит произвольный порядок и не может придумать красивого синтаксиса/ключевого слова.

Изменить: чтобы снова просмотреть эти пробелы в функциональности уровня выражения/типа:

  • приложение функции: это #6606
  • оператор утверждения ! (#17370): после этого #6606 это решается
  • вычитание типа объединения через защиту типа (#4183): просто общий случай ! выше, с этим #6606 также достижимым через ограничения (например, NotZero выше).
  • распространение/отдых (# 5453) - даже если кортежами нельзя манипулировать до тех пор, пока они не приземлятся, аналогичные ArrayLike s могут, см. операции List в моем gist . с этим #6606 я теперь думаю, что мы могли бы извлекать параметры из непримененных функций, хотя для их извлечения в момент применения (т.е. для получения их точных входных значений) все равно потребуется 5453.

Короче говоря, если бы это предложение было реализовано, я бы сказал, что разрыв в функциональности между уровнями выражений и уровнями типов не стал бы слишком большим аргументом в пользу предложенного здесь предложения, основанного на выражениях. Если бы 5453 тоже были там, я бы больше не мог думать ни о чем там. И обратите внимание, что за редкими исключениями обходной путь, указанный в исходном посте, все еще будет действовать.

Теперь аргумент, который все еще можно легко привести в пользу этого, будет заключаться в том, что в варианте со вкусом выражений, даже до того, как уровень типов догонит операторы зеркального отображения, эта функциональность будет представлена ​​​​в том же синтаксисе , что и в JS (без обходных путей), сокращение кривой обучения.

Редактировать 2:

Я только что понял, что трюк с предложением выражений по переносу типов на уровень выражения, 1 as any as MyType , логически должен работать и для самой функции.

Это, вероятно, означает, что фактическая функциональность, предоставляемая обоими вариантами, кажется несколько похожей, внешние различия в основном состоят из typeof myVar (вариант типа) и myVar (вариант выражения) для использования переменных в приложении функции; использовать в них типы MyType (вариант типа) против 1 as any as MyType (вариант выражения, альтернатива declare let a: any; , затем <MyType>a ).

Изменения AST также кажутся довольно управляемыми. Варианту выражения просто нужен конъюнкт typeof , чтобы вместо этого указывать на выражение значения; type будет копировать существующий синтаксис приложения функции ( fn<T>(arg) ) с уровня выражения на уровень типа, подключаясь к существующей реализации, как было предложено Райаном выше.

Я думаю, что тогда это сводится к следующему:

Случай для выражения аромата:

  • typeof expr с синтаксисом JS без обходного пути перед поддержкой уровня типа TS

Футляр для типа аромата:

  • никаких критических изменений над приоритетом
  • выражения значений все еще можно захватить с помощью обходного пути (или с помощью TS, если используется другой синтаксис, пока операторы не наверстают упущенное)
  • MyType вместо 1 as any as MyType : нет уровня типа на вашем уровне выражения на вашем уровне типа на вашем уровне выражения.

Одна связанная с этим тема, до сих пор не затронутая здесь, заключалась в том, как обеспечить привязки this в этой функции уровня типа «приложение». Теперь JS делегирует перезапись этого методам Function.prototype , но в его нынешнем виде у них нет способа справиться с этим на уровне типа.

Случайный пример синтаксиса для заданного типа функции F (this: Foo, a: number, b: string) => SomeReturnType , которая в противном случае могла бы быть вызвана как F(MyA, MyB) : F(this: MyFoo, MyA, MyB) .

Вычисление типа возвращаемого значения без перезаписи привязки this по-прежнему будет похоже F(MyA, MyB) , отражая то, как аргумент уровня типа this обычно игнорируется, если вы пытаетесь использовать такую ​​функцию в уровень экспрессии.

Плюсы этого примера синтаксиса:

  • отражает синтаксис объявления

Минусы синтаксиса этого примера:

  • отражает синтаксис объявления

Так, оказывается, это уже в языке!

Не слишком волнуйтесь.

@DanielRosenwasser только что указал мне на # 12146, ошибку, которая позволяет использовать вызовы функций для литералов вместо типов.

_5 минут спустя_

Та да! Ужасная злая вещь, которую мы никогда не должны использовать в производстве. Но заманчиво...

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"));

~ Это заставляет Effort: Difficult по этому вопросу выглядеть немного сомнительно, похоже, они сделали это там случайно. ~ Я читаю больше и чувствую себя глупо, это _трудно_ сделать красиво.

Веселись, @tycho01.

@TheOtherSamP Я пробовал это с TypeScript 2.4.2, и предполагается, что все эти типы равны any .

@pelotom Ага , здесь работает на 2.4.2 и 2.5.0-dev.20170803. Целевой es6 и строгий режим.

image

Похоже, они только что исправили это, хотя, я боюсь, что это может быть моя вина. №17628

@TheOtherSamP Нет, никаких игральных костей. Ну что ж.

@pelotom Странно, он работает в совершенно новом для меня проекте, я не знаю, что отличалось бы от наших настроек.

@TheOtherSamP : ха-ха, это довольно забавно.
По времени похоже, что они начали исправление немного раньше вашего комментария. Ну что ж.

@пелотом :

Я пробовал это с TypeScript 2.4.2, и все эти типы предполагаются любыми.

Его фрагмент, похоже, работает в Playground (2.3.2). В недавней версии за ее пределами ( ^2.5.0-dev.20170626 ) у меня тоже проблемы с воспроизведением.

Это заставляет Effort: Difficult по этому вопросу выглядеть немного сомнительно, похоже, они сделали это случайно там.

Они имели в виду реализацию на основе типов, что означало бы некоторые изменения, в то время как это, похоже, использует язык выражений (-> + , Object.assign , дальнейшие вызовы функций).

@tycho01 Думаю, все началось с того, что я обратил на это внимание в #17618. Ну что ж, это научит меня полусерьезно рассматривать возможность использования его в продакшене.

Они имели в виду реализацию на основе типов, что означало бы некоторые изменения, в то время как это, похоже, использует язык выражений (-> +, Object.assign, дальнейшие вызовы функций).

Да, я был идиотом и не читал весь этот выпуск до тех пор, пока не сказал это. Жаль, что это, вероятно, лучшая версия функции, но я бы хотел, чтобы она была у нас сейчас. Я сильно раздвигаю границы системы типов, и либо это, либо #12424 откроет так много вариантов.

Открыл PR под номером 17961.

@yortus Покрывает ли это случай 'typeof literal'?
TypeScript сегодня не позволяет писать «const x: typeof 1 = 1;»

@NN --- исходное предложение охватывает все выражения, но, насколько я понимаю, «утвержденная» часть охватывает только доступ к свойствам и типы возвращаемых функций.

Даже если бы typeof 1 было разрешено, я не уверен, даст ли это литеральный тип ( 1 ) или более широкий тип ( number ).

TypeScript сегодня не позволяет писать «const x: typeof 1 = 1;»

Почему бы не const x: 1 = 1; ?

@SaschaNaz Я хотел написать что-то вроде

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

Но подобное не работает с литералами:

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

@yortus Хороший вопрос о точном типе. Не думал о буквальных типах..

@NN---: Я считаю, что ваш пример уже работает.

@tycho01 Flow теперь имеет тип $Call , чтобы получить возвращаемый тип функции https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4

Разве разрешение только typeof fn(...) не будет таким же, как разрешение typeof произвольного выражения?

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

за исключением того, что теперь вы создаете функцию времени выполнения только для того, чтобы выяснить тип?

Не совсем. Вы выполняете оценку типа выражения, а не выполнение выражения.

@dyst5422 typeof fn() на самом деле не будет оценивать выражение, он просто даст вам возвращаемый тип.

РЕДАКТИРОВАТЬ: может быть, это то, что вы пытались сказать, не уверен. Но я думаю, что @sky87 говорил об _определении_ функции без какой-либо цели, кроме использования ее в выражении типа, а не _оценки_ ее.

@dyst5422 dyst5422 , как сказал @pelotom , я не имел в виду, что вы выполните функцию. Чтобы уточнить: если вы не разрешаете typeof произвольных выражений, но разрешаете typeof возвращаемого типа функции, что я сделаю, чтобы выяснить тип более сложного expressions заключается в том, чтобы обернуть их в функцию, чтобы я мог запросить тип возвращаемого значения. Это создает функцию во время выполнения только для того, чтобы получить тип возвращаемого значения, и это более шаблонно для написания.

РЕДАКТИРОВАТЬ: вы уже можете определить тип произвольных выражений, это некрасиво, но работает

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

Честно говоря, я не знаю, какой хак я бы предпочел. Я думаю, что лучше всего было бы напрямую запрашивать тип, используя typeof .

А, теперь я следую. Да, я думаю, что предпочтительным способом было бы использовать его как

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

чтобы иметь возможность произвольно выполнять оценку типа выражения

Можно ли будет запросить возвращаемый тип вызова new ? Мой вариант использования: я хочу написать аннотации типов для функции, которая принимает ссылку на любую реализацию PromiseConstructorLike (например, $q или Bluebird) и возвращает обещание, созданное этой реализацией.

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

Можно ли будет запрашивать возвращаемые типы без typeof или нам придется null as FnType ?

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

Извините, если на эти вопросы уже были даны ответы; Я не мог их найти.

Какой смысл в new здесь, разве вы не хотите просто typeof implementation() ?

Нет, потому что вызов implementation() недействителен. PromiseConstructorLike может быть вызван только через new в соответствии с его объявлениями типа. typeof implementation() — это ошибка типа, точно так же, как (typeof implementation)['foobar'] была бы ошибкой типа.

Ссылка на игровую площадку

Можно ли ввести выводимые универсальные типы, как это сделал FlowType? По крайней мере, это может решить проблему получения типа возвращаемого значения функций.

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

@Cryrivers : см. # 14400 для этого подхода. На самом деле это не решает проблему, когда тип вывода зависит от ввода.

Сегодня это снова понадобилось, чтобы намекнуть, что вызов функции будет возвращаться динамически, конечно, хотелось бы, чтобы он получил приоритет.

Оператор ReturnType<T> добавляется к lib.d.ts в TS 2.8 на основе условных типов.

Поскольку ReturnType<T> еще не учитывает типы возвращаемых значений, зависящие от типов аргументов, для справки, вот реализация типа Flow $Call .

редактировать: извините @goodmind , я не знал, что вы уже связались именно с этим.

Я обновил свой предыдущий пост о вариантах использования этого предложения (или его интерпретации вызова типа) на основе последних дополнений TS.
Варианты использования сопоставления с образцом теперь охватываются # 21496, оставляя... случаи, когда мы хотим вычислить типы на основе лямбда-выражений, предоставленных пользователем, например, curry , композиция функций, map , reduce , редактирование линз на основе лямбда... забавная штука. :)

PS @thorn0 : я думаю, что ваш вариант использования Angular теперь может быть заполнен ReturnType (#21496)!

Я думаю, что это должно быть покрыто, разрешив это выражение.
prop2: typeof this.prop1.big.complex;

@mhegazy Есть ли отдельная проблема для отслеживания этого?
Раздражает, что typeof работает для локальных, но не для свойств, работая со статическими свойствами.

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 --- вы всегда можете использовать для этого индексированные типы:

this['x']

@cameron-martin Не работает. Игровая площадка

@tycho01 Tycho01 вывод нового типа и условные операторы потрясающие, но их очень раздражает то, что они не работают для перегрузки функций. Указанная причина заключалась в том, что для разрешения типа функции из возможных требуется что-то вроде этого typeof .

@NN просто используйте const d: this['x'] = 3;

О, классно :)

@NN--- или используйте

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 Я знаю, что местные работают, но я нахожу это уродливым.
Это то же самое, что и ручное сохранение this для обратного вызова function(){} вместо использования лямбда с неявным захватом.

@NN

это некрасиво.

да это просто вариант :)

Условные типы теперь делают это в значительной степени неуместным, поскольку вы можете написать ReturnTypeOf<T> вместе с другими конкретными псевдонимами, если хотите проверить определенный набор аргументов. Они не могут выполнять разрешение перегрузки, но мы не думаем, что эта функция стоит сложности только для этого варианта использования.

@RyanCavanaugh Я полагаю, ты имеешь в виду ReturnType<T> ?

@RyanCavanaugh , к сожалению, разрешение перегрузки - это то, что мне действительно нужно. Есть ли еще одна проблема для отслеживания добавления разрешения перегрузки к условным типам/выводам?

Вы должны быть в состоянии написать это:

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

Однако я не проверял это, и вам понадобится отдельный помощник для каждого количества аргументов.

@ForbesLindesay : something в настоящее время является переменной уровня выражения - ссылка на нее, например, с помощью typeof здесь (или объявление ее как интерфейса) исправляет это. На самом деле мне не удается заставить его давать соответствующие типы возврата (на 2.8.0-dev.20180318 ).

@ForbesLindesay, к сожалению, я не верю, что это работает; механизм вывода выберет перегрузку метода _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

Однако механизм вывода _способен работать с объединениями кортежей:

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

возможно, нам нужно что-то, чтобы разрешить перегрузку -> объект и функцию -> объект, распаковку. Сделайте сопоставление типов и вывод там, затем вернитесь к функции и перегрузите.

@MeirionHughes :

возможно, нам нужно что-то, чтобы разрешить перегрузку -> объект и функцию -> объект, распаковку. Сделайте сопоставление типов и вывод там, затем вернитесь к функции и перегрузите.

Как (a: number, b?: string) => boolean -> { a: number, b?: string } ? Мы пока не можем получить такие имена параметров, но концептуально это становится сложнее для остальных параметров ( (a: number, ...b: string[]) => boolean ), в том числе потому, что мы не можем использовать типы объектов для выполнения порядка.

Порядок, вероятно, имеет большее значение, чем имена, и мы можем выполнять преобразование между параметрами и кортежами. Спреды/опционы также могут немного усложнить ситуацию.

Это сводит проблему к извлечению перегрузок. Перегрузки должны быть пересечением типов функций, таких как ((a: number) => 123) & ((s: string) => 'hi') , поэтому проблема заключается в том, как «развернуть» тип пересечения (например, в тип кортежа) — на данный момент у нас этого нет.

Я нахожу этот путь неудовлетворительным, поскольку он касается варианта использования с перегрузкой, но не говорит о дженериках, но да. Развертка пересечения все еще была пробелом в любом случае.

Поскольку этот вопрос закрыт, есть ли еще новое предложение для частей, которые все еще отсутствуют? Как способ справиться с типом возвращаемого значения в зависимости от аргументов?

Поскольку этот вопрос закрыт, есть ли еще новое предложение для частей, которые все еще отсутствуют?

ничего, о чем я знаю.

Как способ справиться с типом возвращаемого значения в зависимости от аргументов?

не думайте, что у нас есть проблемы с отслеживанием типов вызовов.

Есть ли предварительная поддержка идеи простого добавления приложения функции уровня типа? Я мог бы написать предложение для этого. Синтаксически я думаю, что это самый простой путь.

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 }

Пограничные случаи:

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?

Есть ли предварительная поддержка идеи простого добавления приложения функции уровня типа? Я мог бы написать предложение для этого. Синтаксически я думаю, что это самый простой путь.

Не в настоящее время. Мы действительно не хотим помещать разрешение перегрузки в пространство более высокого порядка; процесс довольно сложен и включает в себя несколько проходов для вывода параметров типа, аргументов контекстного типа и т. д.. выполнение этого в более высоком порядке, во-первых, требует много работы, а во-вторых, вызовет проблемы с производительностью, с которыми мы не готовы справиться на первом этапе. время.

@mhegazy вообще изменилась позиция команды по этому поводу, учитывая недавнюю работу в # 24897 ??

Кажется, существует довольно много проблем, решения которых могут быть сведены к типу $Call , а тип $Call открыл бы дверь относительно прямому способу эмуляции типов более высокого типа; см., например, https://gist.github.com/hallettj/0fde5bd4c3ce5a5f6d50db6236aaa39e (замените использование $PropertyType и $ObjMap на $Call ). РЕДАКТИРОВАТЬ: Дополнительный пример: https://github.com/facebook/flow/issues/30#issuecomment -346674472

Такая функция, возможно, соответствовала бы послужному списку TypeScript в поиске разумного общего решения многих проблем, не так ли?

Условные типы теперь делают это в значительной степени неуместным, поскольку вы можете написать ReturnTypeOf<T> вместе с другими конкретными псевдонимами, если хотите проверить определенный набор аргументов. Они не могут выполнять разрешение перегрузки, но мы не думаем, что эта функция стоит сложности только для этого варианта использования.

@RyanCavanaugh @mhegazy

Я согласен с тем, что можно делать что-то, используя условные типы. Я думаю, что компилятор не сильно усложнится, если мы перепишем User.avatar в User extends { avatar: infer T } ? T : never ? Так, например, мы могли бы написать

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

так как

export type Avatar = User.avatar;

для улучшения читаемости.

Полный пример

Предположим, мы загружаем и преобразовываем некоторые данные и в итоге получаем функцию findUser, подобную этой

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'] }
        ]
      }
    ]
  };
}

Благодаря выводу из сопоставленных типов мы можем извлечь тип из функции следующим образом:

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

Предложение: это должно оцениваться как одно и то же

export type Avatar = User.avatar;

Кроме того, мы могли бы даже утверждать, что User.avatar не должно быть типа never .

Другие примеры

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[];

При сопоставлении вложенного свойства за один раз не очень понятно, что происходит

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

Это бы многое прояснило

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

Угловой шкаф

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 Кажется, ты просто хочешь

export type Avatar = User["avatar"];

который работает сегодня

@lukaselmer Кажется, ты просто хочешь

export type Avatar = User["avatar"];

который работает сегодня

Это именно то, что я искал. Я искал его в документации , но не нашел. Спасибо!

Является ли это частью руководства или есть какая-либо официальная документация о том, как это работает? Я сам довольно хорошо знаком с тем, как его использовать, но когда я пытаюсь направить людей к документации, все, что я могу найти, это typeof guards, которые на самом деле совершенно разные.

Итак, я заметил, что это предложение появилось в 2015 году, и одной из первоначальных целей было каким-то образом получить тип одного свойства интерфейса.

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

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

правильно ли я предполагаю, что это все еще невозможно спустя 5 лет?

@MFry Я думаю, вы ищете этот синтаксис: a['foo']

Знаем ли мы, есть ли решение для этого?

Я пытаюсь получить что-то вроде этого:

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

Привет @maraisr , я не уверен на 100%, чего ты пытаешься достичь. В вашем примере something принимает два типа, но не использует их, а hello - это возвращаемое значение чего-то, что всегда будет void ; Таким образом, doThing никогда не увидит тип string .

Может быть, что-то вроде ниже, что вы хотите?

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';

Ах да - так жаль об этом. Спасибо за оперативный ответ!! :100: @acutmore

void просто указывает, что возвращаемый тип этой функции не имеет значения. Эти 2 типа перенаправляются на другие универсальные типы, которые в конечном итоге используются в аргументах.

что-то типа:

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. 

Моей функции doThing на самом деле все равно, что такое первый ( A ) дженерик, но ей важно, что такое второй ( B ).

Обратите внимание, что something в моем собственном сценарии использования имеет побочный эффект, который считывается doThing .

Так что я не могу просто получить ReturnType функции - мне нужно как-то высосать дженерик созданной функции.


Если вы считаете, что этот запрос выходит за рамки этой проблемы, я продолжу свое путешествие по StackOverflow!

@maraisr спасибо за дополнительную информацию.

Если вы хотите, чтобы doThing мог получить исходный тип B из something , тогда его нужно каким-то образом передать в hello . TypeScript смотрит только на hello и без посторонней помощи не узнает, что это возвращаемый тип something .

Это можно сделать одним из способов:

/** 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 Почему это закрыто? Условные типы не решают этот и многие другие варианты использования, и если это будет объединено, это сделает возможным очень многое.

Я работаю над функцией, которая может превратить любой вызов метода в «бесточечную» версию (пример: [].map(() => n > 5) превращается в map(() => n > 5)([]) , и единственное, чего не хватает, это условных типов и infer не может обнаруживать дженерики, поэтому в универсальных функциях некоторые типы будут отображаться как unknown .

Если бы я мог «вызывать» функции для получения типа ( typeof myFunc(() => Either<string,number>) ), можно было бы иметь эту функциональность (что в настоящее время невозможно) и сделать многие другие вещи намного проще (HKT и т. д.). .)

Является ли сложность очень высокой, чтобы иметь возможность $Call функции (как в потоке)? Я чувствую, что typescript уже делает это автоматически.

@nythrox мы не думаем, что синтаксическая путаница, к которой это может привести, перевешивается случаями, когда вам это нужно, чтобы получить какой-то тип. Конкретный случай разрешения выражения вызова отслеживается в другом месте; предложение в ОП «разрешить любое выражение» не является чем-то, что, по нашему мнению, хорошо подходит для языка.

@RyanCavanaugh , ладно, я понимаю. Спасибо за ответ, знаете ли вы, какие проблемы отслеживаются при разрешении вызова функции?

Я немного поискал и не нашел проблемы для типа утилиты вызова функции; единственная ссылка на один, который я нашел, была в # 20352, который просто ссылался на эту проблему.

Конкретный случай разрешения выражения вызова отслеживается в другом месте.

@RyanCavanaugh Не могли бы сделать ссылку на другое место? 🙂

@tjjfvi #37181 более конкретно касается разрешения функции на основе ее входных данных. Может быть, то, что вы ищете.

@acutmore Это несколько похоже на то, что я искал, хотя я конкретно говорил об утилите $Call в стиле потока или другом синтаксисе, позволяющем реализовать такое. Предложенный там подход странный, но спасибо за ссылку.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги