Typescript: 建议:用 typeof 获取任意表达式的类型

创建于 2016-01-25  ·  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

此解决方法有一些缺点:

  • 很明显是一个kludge,读者不清楚
  • 标识符污染(必须引入像dummyReturnValue这样的变量)
  • 在环境上下文中不起作用,因为它需要一个命令式语句

建议的解决方案

_(注意:此解决方案已在 #4233 中提出,但该问题被标记为“需要提案”,并且还有其他几个密切相关的问题,因此这个单独的问题。)_

允许typeof的操作数是任意表达式。 在if (typeof foo() === 'string')之类的价值位置中, typeof expr已经允许这样做。 但是,当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 :其他语法对于它们的特定情况可能看起来更好,但它们彼此都不同,并且每一种都只解决一个特定的问题。 该提案解决了所有这些问题中提出的问题,并且开发人员不需要学习任何新的语法。

反对:类型位置的表达式令人困惑。

For : TypeScript 已经重载了具有两种含义的typeof ,作为类型查询,它已经接受了类型位置的表达式并在不评估它的情况下获取它的类型。 这只是放宽了对该表达式的限制,以便它可以解决此问题中提出的问题。

反对:这可能被滥用来编写巨大的长多行类型查询。

For :在类型查询中没有充分的理由这样做,但有充分的理由允许更复杂的表达式。 这基本上是 Martin Fowler 的启用 vs 指导

设计影响、问题和进一步的工作

兼容性

这是一个纯粹的向后兼容更改。 所有现有代码均不受影响。 使用typeof的附加功能是可选的。

表现

查看差异,您可以看到变化非常小。 编译器已经知道要查询的类型,这只是将它们呈现给开发人员。 我预计性能影响可以忽略不计,但我不知道如何测试它。

工装

我已经将 VS Code 设置为使用一个 TypeScript 版本,并将该提案作为其语言服务实现,并且就我测试它而言,所有语法高亮和智能感知都是完美无缺的。

.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

最有用的评论

在#17961 上打开了一个 PR。

所有157条评论

对于任何想要在 VS Code 中使用智能感知等快速玩这个的人,这里是一个游乐场 repo

类型 P = typeof 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完全正确!
虽然我们仍然可以用#5185 写typeof foo("abc")
这里"abc"是单例字符串类型

@tinganho我一直在考虑你的想法,我看到我更喜欢这个提案的一些事情,以及我更喜欢你的建议的其他事情。 由于您给出的原因,您的建议是好的(更简单、更清晰的语法,不那么模棱两可,看起来不像函数调用)。 我更喜欢我的提议的一点是,它没有引入任何新颖的语法,因此不会给解析器/检查器增加任何复杂性,并且它还支持更复杂的场景,其中您没有简单的参数类型名称。

我在想,如果有一种非常简略的方式来编写类似于foo(number)语法但使用现有的表达式解析机制的东西怎么办? 所以作为一个实验,我引入了一个新的表达式:_unary as_。 您可以只写as T ,这是(null as T)的简写。 您基本上是在说,_'我不关心值,但我希望表达式具有 X'_ 类型。

通过此更改(我已在Playground repo中实现),您可以编写更接近您建议的语法的内容,但它仍被解析为普通表达式:

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

第二种语法可能称为 _nullary 类型断言_,表达式<T>(<T> null)的简写。

@yortus ,我们上周花了一些时间讨论这个提议。 抱歉没有早点发帖。 共识是 1. 我们存在无法引用某些类型的问题,例如返回函数或类表达式的实例类型。 2. 在类型位置添加表达式不是我们喜欢的。

@tinganho的提议也是我们讨论过的。 我认为它更可口,尽管实施起来可能会更复杂。 添加新的一元运算符或使用强制转换语法并不像仅使用类型名称那样优雅。

今天在slog讨论了很久。 这里的“接受 PRs”是“假设实施不会太疯狂就接受 PRs”

@tinganho的提议看起来相当不错(至少相对于其他选项而言),我们希望看到一个暂定的 PR 来实现这一点。

棘手的是,我们不希望有一个完全独立的代码路径来解析f(number)f(0)的返回类型,但是重载解析算法是完全内置的,假设是它使用的是一组 _expressions_ 而不是一组 _types_。 但是我们认为这应该是直截了当的。

基本的攻击计划是:

  • 解析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)MyClass _type_ 在MyClass _value_ (即构造函数)之前被考虑是有意义的。 前者导致Ret = number ,后者会导致类似error: MyClass is not a type

相同的逻辑是否适用于同时引用 _type_ 和 _const value_ 的名称? 在您的示例中,这意味着类型number将始终优先于 const 值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你能确认第三组用例超出范围吗? 例如来自 OP:

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

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

目前将不支持此用例(使用任何语法),对吗?

我认为这应该通过允许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 。 分叉 impl 的当前输出:

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我的直觉是,具有多个参数但 _also_ 返回匿名类型的东西将非常罕见。 你怎么看?

当 Angular 1 变得稀有时,它会变得稀有,而不是更早。

基本上,您所描述的是典型的 Angular _factory_ 是什么:

具有多个参数但还返回匿名类型的事物

它是一个将依赖项作为参数并创建_service_ 实例的函数。 您可能认为大量参数可能很麻烦,但问题是这些工厂永远不会被直接调用。 DI 容器调用它们。 当某些东西需要其返回值(服务)作为依赖项时,就会调用工厂。 它只被调用一次,因为服务在 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你能检查一下这个想法吗?

您如何在提案中捕捉表达类型?

2016 年 5 月 19 日星期四 12:26 Anatoly Ressin, notifications @github.com 写道:

嗨,我想建议保持 typeof 语法/语义不变,并且
而是实现一个简单的类型访问代数:

让我们想象一下类型语法扩展:

类型 ::= ... | 文字类型 | 类型类型访问 | 守卫
受保护的 ::= "type" id
文字类型 ::= 字符串文字 | 数字字面量 | 对称文字 | 布尔文字
typeAccess ::= typeField | 类型下标 | 类型调用
类型字段 ::= "." ID
typeSubscript ::= "[" type "]"
typeCall ::= "(" [type {"," type}] ")"

范围内的每个标识符可以同时绑定到两个
实体:

  • 编译时类型
  • 运行时值

_type_ 守卫只提取前者。 因此

A类{}
一个:一个

相当于

A类{}
一:A型

在这种情况下,如果我们有一堂课

ABC类{
一个号码;
b(x:数字):字符串;
c:字符串[];
d:[数字,字符串];
}

然后我们可以写

var a: (类型 ABC).a; // numbervar b: (type ABC).b(number); // stringvar c: (type ABC).c[number]; // stringvar d: (type ABC).c[0]; // 数字

它还涵盖了将 _type_ 实体和 _value_ 实体合并到
词法范围内的相同标识符。

界面相同{
x:数字;
}命名空间相同:{
导出类型 x = 布尔值;
导出变量 x:字符串;
}
var a: SAME.x // booleanvar b: (type SAME).x // numbervar b: (typeof SAME).x // string

@RyanCavanaugh https://github.com/RyanCavanaugh ,@sandersn
https://github.com/sandersn你能检查一下这个想法吗?


您收到此消息是因为您订阅了此线程。
直接回复此邮件或在 GitHub 上查看
https://github.com/Microsoft/TypeScript/issues/6606#issuecomment -220378019

你的例子让我想起了 typeof a dotted expression 和 type 属性 type #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>;
}

那么前面提到的一些场景现在可以用_indexed access types_(#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运算符组合,就像现在的 _indexed access types_ 一样。

然而,一些问题/疑问仍然存在:

  • 这个功能会处理一些其他的场景,比如_indexed access types_吗? 如果不会,是否值得实施?
  • 我认为在大多数情况下,根本不需要指定参数类型会很好,因为最常见的情况是“我只想获得没有其他重载的这个函数的类型”。所以也许引入一些新的特定运算符会很好(例如typeofreturnreturnof

看来这在技术上是可行的。 它可能需要重构重载解析/类型参数推断,以便您不需要解析参数。 我不认为泛型会带来太多额外的问题。 要问的一个问题是,如果没有与调用匹配的过载会发生什么。

我认为这个功能是否可取并不明显。 如果一个函数返回一个非常复杂的匿名类型,作为作者命名该类型不是更有帮助,这样就不必以这种方式引用它吗? 我承认这只是基于风格偏好。 我想我不确定这在实践中使用起来有多方便。 我认为这对元素访问来说很好。

类型 P = typeof 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 )来引用该函数的原因是在它的名称可能不可用的情况下,例如 I'我想能够说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有关于此功能的计划吗?

大家好,
对于这个问题,我有另一种可能的解决方案(对不起,如果它已经被建议并且我错过了) - 我在 issue #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);

当我尝试使用类型推断从mapStateToProps函数获取道具类型时,我正在使用不同的解决方法,这对 React 和 Redux 特别有用。 这样就不再需要手动声明和维护 Redux connect注入的 Props 接口,维护繁琐且容易出错。
这项变通方案还将很好地处理其他用例的任何给定函数签名。

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可以帮助从.d.ts文件中键入基于元组的map() ,但鉴于它依赖于表达式语言( 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是的,我很失望它没有解决问题。

我已经打开了一个关于Optional Generic Types Inference的相关问题:

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 中介绍过。

我只是在解释中使用typeofexpressiontypecomprehensionof作为区分这些用法的一种手段。 我更喜欢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 arity),因为您可以递归地定义函数的返回类型应用其重载之一,并带有一些基本情况重载。 这就是 Haskell 中的工作方式,如果在 TypeScript 中拥有这将是一个很棒的功能。

@masaeedu :听起来很有趣。 是的,重载绝对是让这个提议如此有趣的原因——它们可以用来对不同的选项进行模式匹配,包括any后备。 到目前为止,在类型级别上还不可能进行这样的类型检查。

我承认我使用 Ramda 比 Haskell 更多。 但从那个背景来看,我认为 currying 通常不能与可变参数函数很好地结合,因为 curry 需要“知道”是返回结果还是返回另一个函数来处理额外的参数。

您能否展示一些伪代码,说明您如何看待这个想法适用于像Object.assign这样的可变参数函数(跳过像我使用的&Overwrite之类的细节在我的 PoC 中R.mergeAll )?

@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我不能为Object.assign精确地做到这一点,因为正如我所说,这只适用于咖喱函数,但我会尝试为你创建一个大致工作的咖喱assign函数以同样的方式。

我希望他们能概括一下类型代数,例如(a: string) => (b: number) => void只是Func<string, Func<number, void>>的糖,而(a: string, b: number) => void只是Arity<number, Arity<string, void>>的糖,或者类似的东西。 然后我们只需要通用工具来转换类型,以便紧急获得许多有趣的功能。

为三重职位道歉。 @tycho01这是咖喱,“可变参数” 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 能够同样轻松地为 arity > 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>) ),但有时类型系统缺少可以在表达式中表达的类型(spread 和 rest 类型是在之后引入的对应的表达式已经落地)。

谢谢@tycho01 ,你把这一点带到了promised PR(我实际上是在你在那里发表评论后才来到这里的),这确实表明了如此简单和通用的功能如何以非常优雅的方式覆盖更复杂的场景而无需烘烤以非常具体的行为融入语言。

我认为扩展的typeof是类型系统表达能力的真正改变者,最喜欢映射类型/ keyof做到了。

@Igorbek :感谢您的详细说明,我知道您从哪里来。 也许这两种方法服务于不同的用例。

我认为您比今天的原始帖子更好地展示了原始提案的今天的价值,因为当前的 TS(或者,好吧,Playground 的2.3.3 )可以使许多原始场景已经工作:

// 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> whateverwhatever 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 _is_ 一种类型
  • 如果采用基于表达式的变化,则会引入歧义:

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

我不知道所有剩余的用例,它们似乎还没有被发现,但对我来说最无与伦比和最有吸引力的是:

  • _conditional mapped types_ 比 #12424 更强大,因为typeof将使用标准重载决议; @tycho01promised类型 PR #17077 中展示了一个优雅的示例
  • 在泛型类型的上下文中获取函数/方法返回的类型(如我之前所示)

@masaeedu :好问题。 简短的版本几乎就是@Igorbek所说的; 有关已解决的具体挑战的更多详细信息,您可以在#16392 中的“最需要的功能”列表中找到一个简短列表,我试图将未解决的挑战与可以实现它们的提案联系起来。

这包括:

  • 诸如 AngularJS 使用的工厂函数,请参阅此处原始帖子中链接的三个线程。 - 实际上,考虑到新内置的ReturnType ,这应该没问题。
  • 给定已知的输入,您可以突然计算出reduce / map / filter / find之类的确切返回类型——任何涉及迭代和检查的东西。 stdlib.d.ts会从中受益,像 Ramda/Lodash 这样的 FP 库也会受益。 在实践中,并不是所有的输入都是已知的(比元组更多的类似列表的数组),但是对对象的mapfilter操作也可以比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 语言使用长度安全的向量/矩阵运算进行广告宣传,但这仅需要用于高阶函数。
  • 一种使用上述约束 + IsUnion对输入类型表达Union<T> / NonUnion<T>约束的方法。 ——那种类型有点坏了,我不知道该怎么办了。
  • 同样给定#5453 ,这也有助于键入诸如curryFunction.prototype.bindPromise.all之类的函数(不是详尽的列表,只是其他人提出的一些具有挑战性的函数)。
  • #13500中提出的switch逻辑,这里通过函数重载模式匹配解决
  • 那些映射的条件类型是 #12424 和 #17077 ( promised ) 的主题,也就是类型级别的类型检查。 然而,这并不能解决它——即使它可以用于在今天的任何地方生成布尔文字,直到这个 #6606 登陆我们仍然无法在类型级别上基于此类布尔文字操作/执行条件反正。
  • 检查对象类型上的字符串索引是否存在,例如跨操作保留它(例如OmitOverwrite来自 #12215),请参阅我在 #12424 中的评论
  • 基于上述字符串索引检查,一种修复对象类型属性访问的方法,例如检查匹配对象原型方法名称的字符串(例如toStringtoLocaleString )将解析为字符串索引,而不是与原型方法相比,可能会修复与toString对象访问相关的故障,这些故障在以前依赖于故障内置对象属性访问的所有其他类型操作中。

编辑:自撰写本文以来,已删除由条件类型 (#21496) 填充的用例。

@Igorbek提出的表达式优先方法将(以可能破坏上述列表中的某些未知应用程序为代价——尽管我害怕被画到角落里,但我还没有想到任何具体的东西) 还承诺缩小 value 和 type 级别之间的差距,目前包括 this function application (this #6606), spread/rest (#5453), assertion operator ! (#17370), union type通过类型保护进行减法(#4183,否则可以通过上述约束实现),可能还有更多我无法想到的。

我想这整个权衡可能会引发一些问题,例如为什么要以两种方式访问​​所有相同的功能(实际类型级别,嵌入类型级别的表达式级别访问)。

编辑:用额外的潜在挑战更新了我上面的比较评论。

编辑2:

不幸的是,原来的帖子会让你相信这只会允许编写一些边缘情况而没有一些null &解决方法,而实际上,这是目前阻碍 TS 的关键特性,没有已知的环境上下文解决方法.

这意味着它会影响在单独的.d.ts文件中编写的 TS,这不仅适用于stdlib.d.ts ,而且适用于与原始 JS 项目分开编写的所有 DT 类型,这几乎不可能改变而 JS 库也希望对 Flow 等替代方案保持开放。

(这对于.ts类型并没有好多少,因为使用 OP 的“解决方法”的类型不能被参数化,例如在不影响表达式级别的情况下组合成更高级别的可重用类型。)

@tycho01感谢您的名单; 那里有很多我需要消化的链接。 我真的很想对类型进行模式匹配,#6606 是一个很好的、实用的解决方案。 但是在我看来,值和类型级别的事物之间的混淆越来越多,而#6606 不会改善这方面的事情。

需要这个特性的原因是因为我们无法构造对应的类型表达式:

  • 函数类型的返回类型,当与某些类型的参数一起应用时
  • (在typeof K[L]之前)对象类型上的属性类型,它对应于字符串文字类型的键
  • (可能是其他人,我需要更仔细地查看您的列表)

我觉得实际上应该可以为这些构造纯粹的类型级表达式,而无需将值级构造和类型级表达式混合在一起。 如果像(a: A) => BA | B这样的类型是Func<A, B>Union<A, B>等简单参数化类型的糖,那会很好,并且我们有用于操作参数化类型的通用工具(HKT、fundeps 或类型族等)。

我知道过度关注稳健性并不是​​ TypeScript 的目标之一,但现在有很多类型级别的概念以不透明的方式相互作用。 某种类型的形式化,以及为类型如何与其他类型交互(子类型化、解构等)规定机械规则的方式将有很长的路要走。

我觉得实际上应该可以为这些构造纯粹的类型级表达式,而无需将值级构造和类型级表达式混合在一起。

哦,是的,我个人根本不打算求助于使用价值级别的构造——这就是我正在尝试的全部。 如果价值层面缺一不可,我自己可能就在这里的表情阵营。 :P

我猜价值和类型级别之间的差距预计会缩小(TC39 比 TS 移动得更快吗?),并且 afaik 差距确实已经有出色的类型级别提案(请参阅我之前帖子的底部)。

哎呀,我意识到为相当多的功能构建类型将超出大多数用户的体验。
我的看法是,我希望标准库和 FP 库的类型非常好,以至于 TS 用户可以编写它们并自动为它们处理推理。
TS 只有几个类型级别的运算符,但用它们解决实际问题(主要示例是 #12215 的Overwrite / Omit )对于休闲 Web 开发人员来说似乎有点火箭科学。 哎呀,直到最近我们才知道,它们甚至还没有原型/索引/符号证明。

如果像 (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>;

所以嗯,是的,还没有解决它,但刚刚意识到 kube 的 #14400 实际上可以在那里提供帮助。 我今天早些时候也在那儿见过你!
实际上函数也是如此——这是一个很好的提醒,#14400 可能不仅在那里做函数返回类型,而且还做参数类型。

这种方法目前需要使用重载,这是不幸的,因为它不能扩展,但是是的。 为了让它再次通用,我们基本上会使用这些 #6606 模式匹配重载来触发不同 arities 的正确选项。
我想人们可以使用它来一般地将它们转换为元组类型,然后使用我的增量方法对它们进行迭代,以某种方式对它们进行操作。
对于联合,我一直希望有一种更漂亮的方式来转换为元组类型。 虽然它会添加一个任意顺序,但也想不出漂亮的语法/关键字。

编辑:再次检查那些表达式/类型级别的功能差距:

  • 函数应用:this #6606
  • 断言运算符! (#17370): 在此之后 #6606解决
  • 通过类型保护进行联合类型减法(#4183):只是上面!的一般情况,这个 #6606 也可以通过约束来实现(例如上面的NotZero )。
  • spread/rest (#5453) - 即使元组在到达之前不能被操纵,类似的ArrayLike s 可以,请参阅我的gist中的List操作。 有了这个#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 flavor 会将现有的函数应用程序语法( fn<T>(arg) )从表达式复制到类型级别,并按照上面 Ryan 的建议挂钩到现有的实现。

我认为它归结为以下几点:

表达风味的案例:

  • 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 this的情况下计算返回类型仍然类似于F(MyA, MyB) ,这反映了如果您尝试在表达水平。

此示例语法的优点:

  • 反映声明语法

此示例语法的缺点:

  • 反映声明语法

所以,事实证明,这已经是语言了!

不要太激动。

@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看起来有点可疑,看起来他们在那里是偶然做的。~我读多了觉得很傻,这_is_很难做好。

玩得开心@tycho01。

@TheOtherSamP我用 TypeScript 2.4.2 试过这个,所有这些类型都被推断为any

@pelotom Huh,它在 2.4.2 和 2.5.0-dev.20170803 上工作。 目标 es6 和和严格模式。

image

看起来他们刚刚修复了它,我担心这可能是我的错。 #17628

@TheOtherSamP不,没有骰子。 那好吧。

@pelotom这很奇怪,它对我来说是一个全新的项目,我不知道我们的设置会有什么不同。

@TheOtherSamP :哈哈,这很有趣。
不过,从时间上看,他们似乎在您发表评论之前就开始了修复。 那好吧。

@pelotom

我用 TypeScript 2.4.2 试过这个,所有这些类型都被推断为 any。

他的片段似乎在 Playground (2.3.2) 中工作。 在它之外的最新版本( ^2.5.0-dev.20170626 )上,我也无法复制。

这使得这个问题上的Effort: Difficult看起来有点可疑,看起来他们在那里是偶然做的。

他们指的是基于类型的实现,这意味着一些变化,而这似乎使用表达式语言(-> +Object.assign ,进一步的函数调用)。

@tycho01我_认为_这一切都是从我在#17618 中引起注意的开始。 哦,好吧,这将教会我半认真地考虑在生产中使用它。

他们指的是基于类型的实现,这意味着一些变化,而这似乎使用了表达式语言(-> +,Object.assign,进一步的函数调用)。

是的,我是个白痴,直到说完之后才通读整个问题。 很遗憾,这可能是该功能的更好版本,但我希望我们现在可以拥有它。 我经常突破类型系统的限制,无论是这个还是 #12424 都会打开很多选项。

在#17961 上打开了一个 PR。

@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正在谈论 _defining_ 一个函数,除了在类型表达式中使用它,而不是 _evaluating_ 它。

@dyst5422 ,正如@pelotom所说,我并不是说您会执行该功能。 详细说明:如果您不允许任意表达式的typeof但您允许函数的返回类型的typeof ,我将做些什么来找出更复杂的类型表达式是将它们包装在一个函数中,以便我可以询问它的返回类型。 这会在运行时创建一个函数,尽管只是为了获取它的返回类型,而且编写起来更加样板。

编辑:您实际上已经可以弄清楚任意表达式的类型,这很丑但有效

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

老实说,我不知道我更喜欢哪种黑客。 我认为最好的办法是能够使用typeof直接询问类型。

啊,我现在跟着。 是的,我认为最好的方法是能够将其用作

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

能够任意进行表达式类型评估

是否可以查询new调用的返回类型? 我的用例:我想为接受对任何PromiseConstructorLike实现(例如 $q 或 Bluebird)的引用并返回由该实现构造的 Promise 的函数编写类型注释。

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。 它实际上并没有解决输出类型取决于输入的问题。

今天终于又需要这个来暗示对函数的调用会动态返回什么,当然希望它会得到优先权。

在 TS 2.8 中, ReturnType<T>运算符被添加到lib.d.ts中,由条件类型提供支持。

由于ReturnType<T>尚未考虑依赖于参数类型的返回类型,作为参考,这里是 Flow 的$Call类型实现。

编辑:对不起@goodmind ,我没有意识到你已经链接到了。

我根据最近的 TS 添加更新了我之前关于此提案(或其类型调用解释)的用例的帖子
#21496 现在涵盖了模式匹配用例,留下了......我们想根据用户提供的 lambda 计算类型的情况,例如curry ,函数组合, mapreduce ,基于 lambda 的镜头编辑……有趣的东西。 :)

PS @thorn0 :我认为您的 Angular 用例现在可以用ReturnType (#21496) 填充!

我认为这应该通过允许这种表达来涵盖。
prop2: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新的类型推断和条件非常棒,但也非常烦人,因为它们不适用于函数重载。 给出的原因是它需要像这样的东西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我知道本地工作,但我觉得这很难看。
这与手动为 'function(){}' 回调保存 'this' 而不是使用带有隐式捕获的 lambda 相同。

@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

不过我还没有测试过,你需要一个单独的助手来处理每个数量的参数。

@ForbesLindesaysomething当前是一个表达式级变量——在此处使用例如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

然而,推断机制_is_能够处理元组联合:

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 守卫,这真的是完全不同的

所以,我注意到这个提议从 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

@mar​​aisr我不是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只是表示该函数的返回类型无关紧要。 这两种类型被转发到其他泛型类型,最终在参数中使用。

就像是:

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能够从something获取原始的B类型,则需要以某种方式将其传递给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>) ),那么就有可能拥有这个功能(目前是不可能的),并使许多其他事情更容易做(HKTs等)。 .)

能够$Call一个函数(如流中)的复杂性是否非常高? 我觉得打字稿已经自动完成了。

@nythrox我们不认为这可能导致的语法混乱被您需要它来获取某种类型的情况所抵消。 解决调用表达式的具体情况在别处跟踪; OP 中关于“允许任何表达”的提议不是我们认为适合该语言的内容。

@RyanCavanaugh哦,好吧,我明白了。 感谢您的回复,您知道跟踪解决函数调用的问题是什么吗?

我搜索了一下,没有发现函数调用实用程序类型的问题; 我发现的唯一引用是在#20352 中,它刚刚链接回这个问题。

解决调用表达式的具体情况在别处跟踪

@RyanCavanaugh介意链接到其他地方吗? 🙂

@tjjfvi #37181 更具体地是关于根据其输入解析函数。 可能是您正在寻找的。

@acutmore这有点符合我正在寻找的内容,尽管我专门谈论的是流式$Call实用程序或其他能够实现此类的语法。 建议的方法很奇怪,但感谢您的链接。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

siddjain picture siddjain  ·  3评论

Roam-Cooper picture Roam-Cooper  ·  3评论

wmaurer picture wmaurer  ·  3评论

kyasbal-1994 picture kyasbal-1994  ·  3评论

MartynasZilinskas picture MartynasZilinskas  ·  3评论