建议
🔍 搜索词
函数推断返回类型别名
✅ 可实施性检查清单
我的建议满足以下准则:
- 这不会对现有的TypeScript/JavaScript代码造成破坏性的改变
- 这不会改变现有JavaScript代码的运行时行为
- 这可以在不根据表达式的类型发出不同的JS的情况下实现
- 这不是一个运行时特性(例如库功能、带有JavaScript输出的非ECMAScript语法、JS的新语法糖等)
- 这个特性将与TypeScript's Design Goals的其他部分一致。
⭐ 建议
如果一个函数能够为其返回类型内联声明一个类型别名,那将会非常方便。这种可能的语法如下:
const createCounter = (): infer Counter => {
const result = {
count: 0,
increment: () => ++result.count,
};
return result;
};
这将自动创建一个类型别名,允许像下面这样使用:
import { Counter, createCounter } from './counter';
📃 激励示例
目前,类声明会自动为生成的构造函数函数发出一个类型。然而,如果使用标准函数创建对象,这是不可能的。函数的推断返回类型可以像下面这样进行别名:
// aliased type:
// type Counter = {
// get: () => number;
// increment: () => number;
// }
export type Counter = ReturnType<typeof createCounter>;
const createCounter = () => {
const result = {
count: 0,
increment: () => ++result.count,
};
return result;
};
这是一个强大的模式,因为它允许实现成为类型的源引用,而不是相反的方式。然而,尽管别名类型是从函数派生的,但函数本身并不返回别名类型。
// type is:
// const counter: {
// get: () => number;
// increment: () => number;
// }
// NOT Counter
const counter = createCounter();
为了使用派生类型,有两种选择,两种都很笨拙:
- 将责任交给调用者明确使用该类型:
import { Counter, createCounter } from './counter';
const counter: Counter = createCounter();
- 创建一个多余的高阶函数:
export type Counter = ReturnType<typeof createCounterInternal>;
export const createCounter = (): Counter => createCounterInternal();
const createCounterInternal = () => {
// counter impl
};
💻 用例
任何返回复杂对象的函数都可以从这个特性中受益。
6条答案
按热度按时间kkbh8khc1#
诚然,我不知道这背后有多少复杂性,但作为一个语言特性,如果能将其用作泛型类型的内部别名,特别是在承诺的情况下,那将是非常好的:
oyt4ldly2#
我一直在研究一个JS项目,调查将其迁移到Typescript会有多大难度,这让我想起了这个功能请求,因为我注意到这个功能会让迁移变得容易得多。
基本上有大量的文件以这种模式创建对象:
还有大量的不同文件也以类似的方式创建对象,但它们需要其他函数返回的对象:
忽略“expando赋值”模式(顺便说一下,我希望TS能更广泛地支持它),只需添加
infer CoreContext
来定义类型名称:稍后使用它:
对于这个JS到TS的迁移来说,这将是一个巨大的节省时间的方法。
编辑:
如果这样的构造也可以作为迁移过程中的中间步骤添加到JSDoc注解中,以及为当前的JS文件提供更好的IDE体验,那将会非常有用:
iqih9akk3#
并非我想在这里给自己的特性设个陷阱,但这是一个很大的范围扩展,因为你在那里使用的语法不是TypeScript目前可以推断出来的。重要的是要指出,在声明时,任何值的类型都是“固定”的。它不能像添加值一样迭代地扩展类型。
不过,这也是一个特性。我喜欢TypeScript的类型推断之处在于,它自然地鼓励声明式风格而不是命令式。创建一个对象并以命令式方式向其中添加内容需要你在空对象上放置一个过于宽松的类型,如
Record
或any
,然后用兼容的值“填充它”。例如,你示例的最直接转换是类似这样的:
函数推断出的返回类型始终是“返回的东西的类型”。
同样,如果你只是声明它为:
函数推断出的返回类型是
any
。如果你将其转换为声明式,TypeScript可以推断出上下文类型:
如果绝对需要命令式创建(通常不需要),你可以始终将字段作为值创建,然后返回整个声明式对象:
这种转换纯粹是语法上的,所以不会很难做(尽管在没有一些使用正则表达式的巧妙查找/替换的情况下会很繁琐)。后两种情况都符合这个功能请求,因为在整个应用程序中,你可能不希望将其称为:
所以这将使得上述任何一个都可以轻松地声明为:
mzillmmw4#
我并不是想在这里自己给自己的特性找茬,但这是一个很大的范围扩展,因为你在那里使用的语法不是TS目前可以推断的东西。
是的,抱歉。现在TS只能在JS文件中推断它。在TS文件中这样做完全是不同的功能请求。我们在这里不要讨论它。
我应该准备一些没有这种语法的例子。我只是在看一些JS项目,它一直留在我的脑海里 😅
1szpjjfi5#
我想讨论在哪里可以使用
infer Type
语句,以及它的语义应该是什么样的。对我来说,这个
infer Type
特性应该在它能做的事情上非常有限。我认为心智模型应该是:Type
" 替换 "infer Type
"以下是一些我想到的场景:
这是 OP 描述的主要用例。在我看来最有用的。
我认为
infer
应该只在作用域(这里是模块的作用域)中引入别名。如果你想导出它,那么你需要手动导出它(export { FunResult }
)。这段代码基本上等同于:我们是否允许在变量声明中使用
infer Type
?它会有多大用处?这段代码等同于当前正在工作的代码:
我不确定这种场景有多大用处。但这段代码的语义对我来说很清楚(我们已经在函数体中定义了类型别名,而且很明显这些类型仅限于函数体内部)
(infer T)[]
)就像条件类型一样,我希望
infer
能够“选择”一个类型的一部分(如Type
来自Array<Type>
)"otherFunction" 的主体等同于当前正在工作的代码:
总的来说,我认为
infer Type
这样的行为的清晰度和可理解性对于程序员来说应该是相当明显的。ergxz8rk6#
我想讨论一下
infer Type
语句可以在哪里应用,以及它的语义应该是什么样的。infer
关键字的语义已经存在于条件类型中:虽然文档中没有正式的定义,但可能如下所示:
语义保持不变。这只会改变语言 语法,以便在不同的地方使用它。
对我来说,这个
infer Type
功能应该在它能做什么方面非常有限。它已经是这样了。这次更改只会添加一个额外允许的位置,那就是函数返回类型。
我认为心理模型应该是:
心理模型应该与
class
相同。剥离 ES6 类的语法糖,类实际上只是一个构造函数。TypeScript 提供了额外的语法糖来定义:声明一个类可以访问这两个。然而,类是相当有限的,这将允许类似方便的 "创建者" 函数,它们要灵活得多。
我认为
infer
应该只在作用域(这里是模块的作用域)内引入别名。如果你想导出它,那么你需要手动导出它(export { FunResult }
)。这段代码基本上等同于:这完全抵消了其目的。调用者无法使用别名,只能使用匿名推断类型,因此在不导出类型别名的情况下导出函数与省略类型完全相同。
当你声明一个类时,值和类型总是共享一个词法范围。试想一下如果它们不是这样,而是需要显式的
export type ClassName
我们是否应该允许在变量声明中使用
infer Type
?绝对不行。从概念上讲它是反转的。在赋值语句的“左手”进行推断的是变量的类型,而不是描述表达式结果的类型。“右手”表达式首先被评估,总是产生特定、明确类型的输出。即使它是匿名的,如
any
或unknown
,它仍然是一个具有具体类型的值。如果你碰巧将其分配给一个变量,该变量的类型反映了该值的类型,而不是对其的定义。在这里
someFun
"拥有"类型。分配给其结果的任何变量都应该是那种类型。别名会产生混淆,而不是清晰度。唯一希望在消费者端有一个别名的原因是因为someFun
是外部代码,缺少或错误地定义了类型。即使如此,你还是最好将函数 Package 在另一个定义更好类型的函数中:然后将变量分配给那个“更正确的”类型: