问题
这个问题很明显。但是为什么i() (不变量)函数没有完成它的工作呢?它必须从undefined中删除Database | undefined,因为如果是this.#storage[name] undefined,则函数i()会抛出一个错误。还是没有?
在打字稿中没有as就能解决这个问题吗?
错误
Type 'Partial<Storage>[Name]' is not assignable to type 'Storage[Name]'.
Type 'Database | undefined' is not assignable to type 'Database'.
Type 'undefined' is not assignable to type 'Database'.
(2322)代码
class Database {}
// ----------------
function i(condition: any, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
// ----------------
class Context {
#storage: Partial<Context.Storage> = {};
assignStorage<Name extends keyof Context.Storage>(name: Name, storage: Context.Storage[Name]): this {
this.#storage[name] = storage;
return this;
}
storage<Name extends keyof Context.Storage>(name: Name): Context.Storage[Name] {
i(this.#storage[name], 'CONTEXT_STORAGE_NOT_ASSIGNED');
return this.#storage[name]; // !!!TYPESCRIPT ERROR!!!
}
}
namespace Context {
export interface Storage {
database: Database;
}
}
export default Context;发布于 2021-06-23 11:31:46
你有两个选择:
as Context.Storage[Name]Partial并强制忽略未定义的值The issue
您的i()函数运行良好,不是问题所在。
问题是Partial,它基本上每个属性都有一个返回类型的Context.Storage[Name] | undefined,这与Context.Storage[Name]不匹配,因为undefined缺少一些属性(很明显)。
因此,您的断言是为了验证您正在检查的属性是否确实已定义。但是,对于类型记录来说,这还不够,因为它希望确保值的类型是您想要返回的类型,而且由于Partial部件,确保定义它并不能保证它是类的相同类型的属性。
不想要的解决方案
我不认为有可能在运行时验证值的类型。但是您可以通过as Context.Storage[Name]绕过这个问题,因为您的断言是说如果定义了它,那么它就必须是这种类型(尽管您要求采用不同的方式,但我认为没有一个与这个特定问题相关)。
class Database {}
// ----------------
function i(condition: any, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
// ----------------
class Context {
#storage: Partial<Context.Storage> = {};
assignStorage<Name extends keyof Context.Storage>(name: Name, storage: Context.Storage[Name]): this {
this.#storage[name] = storage;
return this;
}
storage<Name extends keyof Context.Storage>(name: Name): Context.Storage[Name] {
i(this.#storage[name], 'CONTEXT_STORAGE_NOT_ASSIGNED');
return this.#storage[name] as Context.Storage[Name];
}
}
namespace Context {
export interface Storage {
database: Database;
}
}
export default Context;
要确认打字本在此问题上是正确的,请尝试以下代码:
let brokenStorage: Partial<Context.Storage> = {
database: 2 // value is not a Database
}在这里,我们有一个损坏的存储,其中包含一个数字作为Database。可以在没有问题的情况下初始化它,这就是类型记录所警告的内容。因为如果另一个人来找您,并向您的storage字段中添加了一个填充错误值的方法,类型记录不能阻止它们,因为Partial类型确实允许这样的行为。
但它可以提醒你,事实上,你可能没有你所要求的类型在你的领域。
替代解决方案
另一种方法是使接口的所有属性都是可选的。这样,您将避免使用Partial并直接使用您的接口:
class Database { }
// ----------------
function i(condition: any, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
// ----------------
class Context {
#storage: Context.Storage = {};
assignStorage<Name extends keyof Context.Storage>(name: Name, storage: Context.Storage[Name]): this {
this.#storage[name] = storage;
return this;
}
storage<Name extends keyof Context.Storage>(name: Name): Context.Storage[Name] {
i(this.#storage[name], 'CONTEXT_STORAGE_NOT_ASSIGNED');
return this.#storage[name]!;
}
}
namespace Context {
export interface Storage {
database?: Database;
}
}
export default Context;
可以看到,我们在返回时添加了!,以忽略未定义的值。因为您的断言已经在检查该值是否已定义。
但是,对于这两种解决方案,您可以通过随机值来破坏存储。
假设我们在上下文类中有这个方法:
breakStorage() {
this.#storage = {
database: 2
};
}这在两种“解决方案”中都有效。因为第一个,我们有一个Partial存储,而第二个,我们有一个有一个可选字段的interface (所以如果这个字段有一个不同的类型,那么它仍然与接口兼容,因为它无论如何都是可选的)。
https://stackoverflow.com/questions/68097550
复制相似问题