首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >打字稿-拿到道具。从部分对象

打字稿-拿到道具。从部分对象
EN

Stack Overflow用户
提问于 2021-06-23 09:54:18
回答 1查看 407关注 0票数 1

问题

这个问题很明显。但是为什么i() (不变量)函数没有完成它的工作呢?它必须从undefined中删除Database | undefined,因为如果是this.#storage[name] undefined,则函数i()会抛出一个错误。还是没有?

在打字稿中没有as就能解决这个问题吗?

错误

代码语言:javascript
复制
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)

代码

TS游乐场

代码语言:javascript
复制
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;
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 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]绕过这个问题,因为您的断言是说如果定义了它,那么它就必须是这种类型(尽管您要求采用不同的方式,但我认为没有一个与这个特定问题相关)。

代码语言:javascript
复制
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;

要确认打字本在此问题上是正确的,请尝试以下代码:

代码语言:javascript
复制
let brokenStorage: Partial<Context.Storage> = {
  database: 2 // value is not a Database
}

在这里,我们有一个损坏的存储,其中包含一个数字作为Database。可以在没有问题的情况下初始化它,这就是类型记录所警告的内容。因为如果另一个人来找您,并向您的storage字段中添加了一个填充错误值的方法,类型记录不能阻止它们,因为Partial类型确实允许这样的行为。

但它可以提醒你,事实上,你可能没有你所要求的类型在你的领域。

替代解决方案

另一种方法是使接口的所有属性都是可选的。这样,您将避免使用Partial并直接使用您的接口:

代码语言:javascript
复制
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;

可以看到,我们在返回时添加了!,以忽略未定义的值。因为您的断言已经在检查该值是否已定义。

但是,对于这两种解决方案,您可以通过随机值来破坏存储。

假设我们在上下文类中有这个方法:

代码语言:javascript
复制
breakStorage() {
  this.#storage = {
    database: 2
  };
}

这在两种“解决方案”中都有效。因为第一个,我们有一个Partial存储,而第二个,我们有一个有一个可选字段的interface (所以如果这个字段有一个不同的类型,那么它仍然与接口兼容,因为它无论如何都是可选的)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68097550

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档