- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
让我们从这个快照开始结束,它描述了所有通过的单元测试,但以红色突出显示,不幸的是类型保真度的损失:
您看到的是 Configurator()
函数提供了一种构建配置的方法,完成后您可以调用 done()
并且键入的配置可用。好消息是它几乎可以工作了。它确实在运行时工作(注意蓝色文本,它显示所有键 - a、b 和 c - 确实已设置)。但是,正如您在底部圆圈区域中看到的,属性 b
未输入,而 a
和c
是。
错过输入 b
的原因来自实现细节,这些细节在某种程度上是可以理解的,但让我们来讨论它们。这是Configurator
代码:
export function Configurator<I extends object>(initial?: I) {
let configuration = () => initial || {};
const api = <C extends object>(current: C): IConfigurator<C> => {
return {
set<V, K extends string, KV = { [U in K]: V }>(key: K, value: V) {
const keyValue = ({ [key]: value as V } as unknown) as KV;
const updated = { ...config, ...keyValue };
configuration = (): C & KV => updated;
return api<C & KV>(updated);
},
done() {
return configuration() as C;
},
};
};
return api(initial || {});
}
界面IConfigurator<T>
每次配置循环都会返回 api
表面和使用set()
能够添加新的键值对,因为它们具有完整的 Typescript 保真度。这部分有效,这就是变量 c
的原因记住属性“a”(参见上面的第 1 项),以及为什么变量 d
记住属性“a”(因为 c 知道它)和属性“c”。
属性“b”(在突出显示为 #2 的行上设置)会在运行时被记住,因为 configuration
在配置外壳中分配的参数与api
的外壳。我们最初只是在对象中分配了值,但是您在这里看到的是一个返回值的函数;我们改变了这一点,因为有时 Typescript 在涉及函数时更擅长推断事物。可惜不在这里。配置类型被固定为一个空对象。因为api
表面类型C
我们可以将配置转换为类型 C
但这意味着类似 b
的属性从类型系统的推断中被孤立。
我的问题有两个:
configuration
的类型这样当我们调用done()
时我们如何确保提供完整的类型支持?configuration
已经丢失了有办法重建吗?正如您从屏幕截图中看到的,在运行时可以识别属性及其类型...似乎这应该导致一种实现完整类型支持的方法,但迄今为止的尝试都失败了(见下文)注意:这是我们为实现上面的#2 所做的尝试。
export type AppendToObject<T, U extends keyof any, V> = {
[K in keyof T | U]: K extends keyof T ? T[K] : V;
};
/** Given a structured run-time object, iterate over keys and append types */
export const inferObject = <T extends object>(v: T) => {
let obj = v;
Object.keys(v).forEach((key) => {
const value = (v as any)[key];
obj = fixup(v, key, value);
});
return obj;
};
function fixup<V, T extends object, K extends string = string>(obj: T, key: K, value: V) {
return { ...obj, [key]: value } as AppendToObject<T, K, V>;
}
注2:IConfigurator
的代码被遗漏了,虽然如果我们找到解决方案,它需要更新,但我不认为这是问题的根源。尽管如此,我想不出任何人应该相信我这一点的理由。
interface IConfigurator<C> {
set<V, K extends string, KV = { [U in K]: V }>(key: K, value: V): IConfigurator<C & KV>;
done(): C;
}
最佳答案
TypeScript 不支持既返回值又缩小调用该方法的对象类型范围的方法。
传统上(TS 3.7 之前)我想说,如果您希望 TypeScript 的类型检查器正确跟踪类型,您需要使用纯粹的“流畅构建器”模式,其中链中的每个值仅使用一次。或者,如果您确实多次使用它们(例如您的 c.set()
示例),则需要使这些方法不可变,以便 c.set()
的返回值是唯一受影响的。传统上,Typescript 无法捕获对象方法改变其值状态的想法。
从 TypeScript 3.7 开始,您可以使用 assertion functions编写一个配置器,进行相反的操作;您忽略该方法的返回值,并继续重新使用原始对象。每次调用c.set()
时,它都会缩小c
的类型。
您当前的 set()
方法旨在执行这两件事,但 TypeScript 只能真正支持其中之一。 (有状态版本有一个恼人的警告)
纯构建器版本与现有的 set()
方法的工作方式如下:
let o = Configurator()
.set("a", 5)
.set("b", "foobar")
.set("c", { hello: "world" })
.done();
o.a // okay
o.b // okay
o.c // okay
在这里,我们在使用一次中间对象后就将其丢弃;我们从不重复使用它。
有状态方法使用 TypeScript 3.7 中引入的断言函数。这些函数允许您将 void
返回函数标记为对其参数之一的类型具有缩小效果……或者在方法的情况下,对对象类型具有缩小效果有方法。我将更改您的 set()
方法的签名:
interface IConfigurator<C = {}> {
add<A extends {}>(dictionary: A): IConfigurator<A & C>;
set<V, K extends string, KV = { [U in K]: V }>(
key: K, value: V
): asserts this is IConfigurator<C & KV>;
done(): C;
}
注意断言这是...
返回类型。然后你可以像这样使用配置器:
let c: IConfigurator = Configurator();
// ~~~~~~~~~~~~~~~ <-- note this annotation
c.set("a", 5);
c.set("b", "foobar");
c.set("c", { hello: "world" });
const o = c.done();
o.a // okay
o.b // okay
o.c // okay
每次调用 c.set()
时,都会缩小 c
的类型,这样当您最终调用 c.done()
,已知返回值具有您设置
的所有属性。令人沮丧的是,您需要为 c
提供显式类型注释才能使其工作。如果您将其关闭,则每次调用 set()
时都会出现令人讨厌的错误(有关详细信息,请参阅 microsoft/TypeScript#36931):
let cBad = Configurator();
cBad.set("a", 5); // error!
//~~~~~~ <-- Assertions require every name in the call target
// to be declared with an explicit type annotation.
关于typescript - 构建推断对象会起作用,直到它不起作用为止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66035496/
尝试使用集成到 QTCreator 的表单编辑器,但即使我将插件放入 QtCreator.app/Contents/MacOS/designer 也不会显示。不过,相同的 dylib 文件确实适用于独
在此代码示例中。 “this.method2();”之后会读到什么?在返回returnedValue之前会跳转到method2()吗? public int method1(int returnedV
我的项目有通过gradle配置的依赖项。我想添加以下依赖项: compile group: 'org.restlet.jse', name: 'org.restlet.ext.apispark', v
我将把我们基于 Windows 的客户管理软件移植到基于 Web 的软件。我发现 polymer 可能是一种选择。 但是,对于我们的使用,我们找不到 polymer 组件具有表格 View 、下拉菜单
我的项目文件夹 Project 中有一个文件夹,比如 ED 文件夹,当我在 Eclipse 中指定在哪里查找我写入的文件时 File file = new File("ED/text.txt"); e
这是奇怪的事情,这个有效: $('#box').css({"backgroundPosition": "0px 250px"}); 但这不起作用,它只是不改变位置: $('#box').animate
这个问题在这里已经有了答案: Why does OR 0 round numbers in Javascript? (3 个答案) 关闭 5 年前。 Mozilla JavaScript Guide
这个问题在这里已经有了答案: Is the function strcmpi in the C standard libary of ISO? (3 个答案) 关闭 8 年前。 我有一个问题,为什么
我目前使用的是共享主机方案,我不确定它使用的是哪个版本的 MySQL,但它似乎不支持 DATETIMEOFFSET 类型。 是否存在支持 DATETIMEOFFSET 的 MySQL 版本?或者有计划
研究 Seam 3,我发现 Seam Solder 允许将 @Named 注释应用于包 - 在这种情况下,该包中的所有 bean 都将自动命名,就好像它们符合条件一样@Named 他们自己。我没有看到
我知道 .append 偶尔会增加数组的容量并形成数组的新副本,但 .removeLast 会逆转这种情况并减少容量通过复制到一个新的更小的数组来改变数组? 最佳答案 否(或者至少如果是,则它是一个错
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
noexcept 函数说明符是否旨在 boost 性能,因为生成的对象中可能没有记录异常的代码,因此应尽可能将其添加到函数声明和定义中?我首先想到了可调用对象的包装器,其中 noexcept 可能会产
我正在使用 Angularjs 1.3.7,刚刚发现 Promise.all 在成功响应后不会更新 angularjs View ,而 $q.all 会。由于 Promises 包含在 native
我最近发现了这段JavaScript代码: Math.random() * 0x1000000 10.12345 10.12345 >> 0 10 > 10.12345 >>> 0 10 我使用
我正在编写一个玩具(物理)矢量库,并且遇到了 GHC 坚持认为函数应该具有 Integer 的问题。是他们的类型。我希望向量乘以向量以及标量(仅使用 * ),虽然这可以通过仅使用 Vector 来实现
PHP 的 mail() 函数发送邮件正常,但 Swiftmailer 的 Swift_MailTransport 不起作用! 这有效: mail('user@example.com', 'test
我尝试通过 php 脚本转储我的数据,但没有命令行。所以我用 this script 创建了我的 .sql 文件然后我尝试使用我的脚本: $link = mysql_connect($host, $u
使用 python 2.6.4 中的 sqlite3 标准库,以下查询在 sqlite3 命令行上运行良好: select segmentid, node_t, start, number,title
我最近发现了这段JavaScript代码: Math.random() * 0x1000000 10.12345 10.12345 >> 0 10 > 10.12345 >>> 0 10 我使用
我是一名优秀的程序员,十分优秀!